From 0688fb19530278e4b1894aa959ff547fc95ca56d Mon Sep 17 00:00:00 2001 From: n30nex Date: Sun, 19 Jul 2026 18:49:35 -0400 Subject: [PATCH 01/10] Overhaul MeshCore Canada site journeys and quality gates --- .github/workflows/apply-approved-boundary.yml | 2 +- .github/workflows/deploy-reviewed-site.yml | 57 + .github/workflows/deploy.yml | 72 - .github/workflows/quality.yml | 156 + .github/workflows/validate-site.yml | 29 - .github/workflows/validate.yml | 68 - .gitignore | 6 + data/communities.json | 804 ++ docs/analyzer/broker-reference.md | 97 +- docs/analyzer/builds/mctomqtt.md | 247 +- docs/analyzer/builds/meshcore-ha.md | 129 +- docs/analyzer/builds/mqtt-firmware.md | 382 +- docs/analyzer/builds/pymc.md | 133 +- docs/analyzer/data-collection-access.md | 128 +- docs/analyzer/iata-codes.md | 214 +- docs/analyzer/intro.md | 189 +- docs/analyzer/location-codes.json | 101 + docs/analyzer/observer-config.json | 48 + docs/analyzer/remoteterm.md | 95 +- .../scripts/add-meshcore-ca-broker.sh | 12 +- .../add-meshcore-ca-packetcapture-broker.ps1 | 9 +- docs/analyzer/troubleshooting.md | 218 +- docs/analyzer/verify.md | 148 +- .../javascripts/analyzer-broker-reference.js | 63 + .../javascripts/analyzer-command-builder.js | 251 + .../javascripts/analyzer-location-codes.js | 104 + .../javascripts/analyzer-method-chooser.js | 68 + docs/assets/javascripts/bootstrap.js | 108 + docs/assets/javascripts/communities.js | 113 + .../regions/modules/configurator-support.js | 63 + docs/assets/regions/regions.css | 11 +- docs/assets/regions/regions.js | 1117 ++- .../regions/vendor/{README.md => README.txt} | 0 docs/assets/styles/analyzer.css | 264 + docs/assets/styles/base.css | 151 + docs/assets/styles/communities.css | 264 + docs/assets/styles/components.css | 242 + docs/assets/styles/devices-builds.css | 236 + docs/assets/styles/shell.css | 102 + docs/assets/styles/tokens.css | 79 + docs/config/editor/app.js | 824 +- docs/config/editor/domain/proposal-types.js | 58 + docs/config/editor/editor.css | 337 + docs/config/editor/index.html | 150 +- .../editor/infrastructure/draft-store.js | 137 + docs/config/index.md | 18 + docs/config/map.md | 18 + docs/config/standard.md | 13 + docs/contributing.md | 95 +- docs/hardware/overview.md | 116 +- docs/hardware/recommended-antenna.md | 124 +- docs/hardware/recommended-companions.md | 144 +- docs/hardware/recommended-repeaters.md | 128 +- docs/hardware/repeater-mounting-options.md | 135 +- docs/hardware/repeater-solar-1w-diy-build.md | 444 +- .../repeater-solar-300mw-diy-build.md | 314 +- docs/hardware/repeater-solar-batteries.md | 62 + docs/hardware/wire-connector-types.md | 64 +- docs/index.md | 164 +- docs/javascripts/submission-form.js | 258 +- docs/meshcore/firmware-heltec-v3-wifi.md | 153 +- docs/meshcore/firmware-rak-custom-display.md | 200 +- docs/meshcore/flash-companion.md | 132 +- docs/meshcore/flash-repeater.md | 174 +- docs/meshcore/flash-room-server.md | 117 +- docs/meshcore/general-faq.md | 148 +- docs/meshcore/general-howto.md | 213 +- docs/meshcore/general-meshcore-roles.md | 117 +- docs/meshcore/general-overview.md | 85 +- docs/meshcore/generate-repeater-id.md | 88 +- docs/meshcore/update-repeater-ota.md | 153 +- docs/privacy.md | 71 + docs/provinces/alberta.md | 197 +- docs/provinces/british-columbia.md | 68 +- docs/provinces/index.md | 410 +- docs/provinces/manitoba.md | 47 +- docs/provinces/new-brunswick.md | 71 +- docs/provinces/newfoundland-and-labrador.md | 47 +- docs/provinces/nova-scotia.md | 68 +- docs/provinces/ontario.md | 146 +- docs/provinces/prince-edward-island.md | 47 +- docs/provinces/quebec.md | 192 +- docs/provinces/saskatchewan.md | 76 +- docs/provinces/territories.md | 47 +- docs/resources/getting-started.md | 135 +- docs/resources/glossary.md | 123 +- docs/resources/links.md | 128 +- docs/robots.txt | 4 + docs/start/choose-a-goal.md | 64 + docs/start/companion.md | 98 + docs/start/get-help.md | 60 + docs/start/index.md | 95 + docs/start/observer.md | 107 + docs/start/repeater.md | 104 + docs/start/room-server.md | 97 + docs/start/verify.md | 88 + docs/stylesheets/extra.css | 131 +- docs/submit-idea.md | 45 +- mkdocs.yml | 188 +- overrides/404.html | 22 + overrides/main.html | 46 + overrides/partials/content.html | 18 + overrides/partials/page-feedback.html | 6 + overrides/partials/page-lifecycle.html | 13 + package-lock.json | 1794 +++++ package.json | 25 + playwright.config.mjs | 62 + requirements-docs.txt | 6 + schemas/analyzer-location-codes.schema.json | 29 + schemas/analyzer-observer-config.schema.json | 48 + schemas/community-directory.schema.json | 377 + schemas/page-metadata.schema.json | 91 + scripts/check-built-links.mjs | 138 + scripts/postprocess-site.mjs | 84 + scripts/run-lighthouse.mjs | 106 + scripts/validate-communities.py | 848 +++ scripts/validate-content.py | 224 + ...lidate-regions.js => validate-regions.cjs} | 2 +- site/404.html | 1346 ---- site/analyzer/builds/mctomqtt/index.html | 1789 ----- site/analyzer/builds/meshcore-ha/index.html | 1780 ----- site/analyzer/builds/mqtt-firmware/index.html | 2644 ------- site/analyzer/builds/pymc/index.html | 1720 ----- site/analyzer/intro/index.html | 2004 ----- site/analyzer/verify/index.html | 1704 ----- site/assets/MeshCore-Canada-Favicon.png | Bin 274340 -> 0 bytes site/assets/MeshCore-Canada-Logo.png | Bin 240402 -> 0 bytes site/assets/images/favicon.png | Bin 1870 -> 0 bytes .../assets/javascripts/bundle.79ae519e.min.js | 16 - .../javascripts/bundle.79ae519e.min.js.map | 7 - .../javascripts/lunr/min/lunr.ar.min.js | 1 - .../javascripts/lunr/min/lunr.da.min.js | 18 - .../javascripts/lunr/min/lunr.de.min.js | 18 - .../javascripts/lunr/min/lunr.du.min.js | 18 - .../javascripts/lunr/min/lunr.el.min.js | 1 - .../javascripts/lunr/min/lunr.es.min.js | 18 - .../javascripts/lunr/min/lunr.fi.min.js | 18 - .../javascripts/lunr/min/lunr.fr.min.js | 18 - .../javascripts/lunr/min/lunr.he.min.js | 1 - .../javascripts/lunr/min/lunr.hi.min.js | 1 - .../javascripts/lunr/min/lunr.hu.min.js | 18 - .../javascripts/lunr/min/lunr.hy.min.js | 1 - .../javascripts/lunr/min/lunr.it.min.js | 18 - .../javascripts/lunr/min/lunr.ja.min.js | 1 - .../javascripts/lunr/min/lunr.jp.min.js | 1 - .../javascripts/lunr/min/lunr.kn.min.js | 1 - .../javascripts/lunr/min/lunr.ko.min.js | 1 - .../javascripts/lunr/min/lunr.multi.min.js | 1 - .../javascripts/lunr/min/lunr.nl.min.js | 18 - .../javascripts/lunr/min/lunr.no.min.js | 18 - .../javascripts/lunr/min/lunr.pt.min.js | 18 - .../javascripts/lunr/min/lunr.ro.min.js | 18 - .../javascripts/lunr/min/lunr.ru.min.js | 18 - .../javascripts/lunr/min/lunr.sa.min.js | 1 - .../lunr/min/lunr.stemmer.support.min.js | 1 - .../javascripts/lunr/min/lunr.sv.min.js | 18 - .../javascripts/lunr/min/lunr.ta.min.js | 1 - .../javascripts/lunr/min/lunr.te.min.js | 1 - .../javascripts/lunr/min/lunr.th.min.js | 1 - .../javascripts/lunr/min/lunr.tr.min.js | 18 - .../javascripts/lunr/min/lunr.vi.min.js | 1 - .../javascripts/lunr/min/lunr.zh.min.js | 1 - site/assets/javascripts/lunr/tinyseg.js | 206 - site/assets/javascripts/lunr/wordcut.js | 6708 ----------------- .../workers/search.2c215733.min.js | 42 - .../workers/search.2c215733.min.js.map | 7 - site/assets/stylesheets/main.484c7ddc.min.css | 1 - .../stylesheets/main.484c7ddc.min.css.map | 1 - .../stylesheets/palette.ab4e12ef.min.css | 1 - .../stylesheets/palette.ab4e12ef.min.css.map | 1 - site/contributing/index.html | 1601 ---- site/index.html | 1521 ---- site/meshcore/antennas/index.html | 1585 ---- site/meshcore/companions/index.html | 1533 ---- site/meshcore/faq/index.html | 1542 ---- site/meshcore/intro/index.html | 1513 ---- site/meshcore/repeater-builds/index.html | 1539 ---- site/meshcore/repeaters/index.html | 1558 ---- site/provinces/alberta/index.html | 1485 ---- site/provinces/british-columbia/index.html | 1467 ---- site/provinces/manitoba/index.html | 1462 ---- site/provinces/new-brunswick/index.html | 1462 ---- .../newfoundland-and-labrador/index.html | 1462 ---- site/provinces/nova-scotia/index.html | 1462 ---- site/provinces/ontario/index.html | 1474 ---- .../provinces/prince-edward-island/index.html | 1462 ---- site/provinces/quebec/index.html | 1462 ---- site/provinces/saskatchewan/index.html | 1462 ---- site/provinces/territories/index.html | 1462 ---- site/resources/getting-started/index.html | 1613 ---- site/resources/glossary/index.html | 1419 ---- site/resources/links/index.html | 1588 ---- site/search/search_index.json | 1 - site/sitemap.xml | 115 - site/sitemap.xml.gz | Bin 441 -> 0 bytes tests/browser/accessibility.spec.mjs | 47 + tests/browser/critical-journeys.spec.mjs | 156 + tests/content/analyzer-journey.test.mjs | 253 + tests/content/community-directory.test.mjs | 240 + tests/content/configurator-ui.test.mjs | 60 + tests/content/devices-builds.test.mjs | 192 + tests/content/learn-content.test.mjs | 146 + tests/content/p0-content-safety.test.mjs | 261 + tests/content/site-postprocess.test.mjs | 52 + tests/content/start-journeys.test.mjs | 196 + tests/content/test_validate_content.py | 76 + tests/editor/community-submission.test.mjs | 30 +- tests/editor/editor-containment.test.mjs | 65 + tests/editor/editor-ux.test.mjs | 164 + tests/editor/region-validator.test.mjs | 2 +- ux-overhaul/DECISIONS.md | 90 + ux-overhaul/IMPLEMENTATION-PLAN.md | 82 + ux-overhaul/ROUTE-MIGRATION.md | 86 + ux-overhaul/STATUS.md | 96 + 214 files changed, 17462 insertions(+), 57002 deletions(-) create mode 100644 .github/workflows/deploy-reviewed-site.yml delete mode 100644 .github/workflows/deploy.yml create mode 100644 .github/workflows/quality.yml delete mode 100644 .github/workflows/validate-site.yml delete mode 100644 .github/workflows/validate.yml create mode 100644 data/communities.json create mode 100644 docs/analyzer/location-codes.json create mode 100644 docs/analyzer/observer-config.json create mode 100644 docs/assets/javascripts/analyzer-broker-reference.js create mode 100644 docs/assets/javascripts/analyzer-command-builder.js create mode 100644 docs/assets/javascripts/analyzer-location-codes.js create mode 100644 docs/assets/javascripts/analyzer-method-chooser.js create mode 100644 docs/assets/javascripts/bootstrap.js create mode 100644 docs/assets/javascripts/communities.js create mode 100644 docs/assets/regions/modules/configurator-support.js rename docs/assets/regions/vendor/{README.md => README.txt} (100%) create mode 100644 docs/assets/styles/analyzer.css create mode 100644 docs/assets/styles/base.css create mode 100644 docs/assets/styles/communities.css create mode 100644 docs/assets/styles/components.css create mode 100644 docs/assets/styles/devices-builds.css create mode 100644 docs/assets/styles/shell.css create mode 100644 docs/assets/styles/tokens.css create mode 100644 docs/config/editor/domain/proposal-types.js create mode 100644 docs/config/editor/infrastructure/draft-store.js create mode 100644 docs/privacy.md create mode 100644 docs/robots.txt create mode 100644 docs/start/choose-a-goal.md create mode 100644 docs/start/companion.md create mode 100644 docs/start/get-help.md create mode 100644 docs/start/index.md create mode 100644 docs/start/observer.md create mode 100644 docs/start/repeater.md create mode 100644 docs/start/room-server.md create mode 100644 docs/start/verify.md create mode 100644 overrides/404.html create mode 100644 overrides/main.html create mode 100644 overrides/partials/content.html create mode 100644 overrides/partials/page-feedback.html create mode 100644 overrides/partials/page-lifecycle.html create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 playwright.config.mjs create mode 100644 requirements-docs.txt create mode 100644 schemas/analyzer-location-codes.schema.json create mode 100644 schemas/analyzer-observer-config.schema.json create mode 100644 schemas/community-directory.schema.json create mode 100644 schemas/page-metadata.schema.json create mode 100644 scripts/check-built-links.mjs create mode 100644 scripts/postprocess-site.mjs create mode 100644 scripts/run-lighthouse.mjs create mode 100644 scripts/validate-communities.py create mode 100644 scripts/validate-content.py rename scripts/{validate-regions.js => validate-regions.cjs} (99%) delete mode 100644 site/404.html delete mode 100644 site/analyzer/builds/mctomqtt/index.html delete mode 100644 site/analyzer/builds/meshcore-ha/index.html delete mode 100644 site/analyzer/builds/mqtt-firmware/index.html delete mode 100644 site/analyzer/builds/pymc/index.html delete mode 100644 site/analyzer/intro/index.html delete mode 100644 site/analyzer/verify/index.html delete mode 100644 site/assets/MeshCore-Canada-Favicon.png delete mode 100644 site/assets/MeshCore-Canada-Logo.png delete mode 100644 site/assets/images/favicon.png delete mode 100644 site/assets/javascripts/bundle.79ae519e.min.js delete mode 100644 site/assets/javascripts/bundle.79ae519e.min.js.map delete mode 100644 site/assets/javascripts/lunr/min/lunr.ar.min.js delete mode 100644 site/assets/javascripts/lunr/min/lunr.da.min.js delete mode 100644 site/assets/javascripts/lunr/min/lunr.de.min.js delete mode 100644 site/assets/javascripts/lunr/min/lunr.du.min.js delete mode 100644 site/assets/javascripts/lunr/min/lunr.el.min.js delete mode 100644 site/assets/javascripts/lunr/min/lunr.es.min.js delete mode 100644 site/assets/javascripts/lunr/min/lunr.fi.min.js delete mode 100644 site/assets/javascripts/lunr/min/lunr.fr.min.js delete mode 100644 site/assets/javascripts/lunr/min/lunr.he.min.js delete mode 100644 site/assets/javascripts/lunr/min/lunr.hi.min.js delete mode 100644 site/assets/javascripts/lunr/min/lunr.hu.min.js delete mode 100644 site/assets/javascripts/lunr/min/lunr.hy.min.js delete mode 100644 site/assets/javascripts/lunr/min/lunr.it.min.js delete mode 100644 site/assets/javascripts/lunr/min/lunr.ja.min.js delete mode 100644 site/assets/javascripts/lunr/min/lunr.jp.min.js delete mode 100644 site/assets/javascripts/lunr/min/lunr.kn.min.js delete mode 100644 site/assets/javascripts/lunr/min/lunr.ko.min.js delete mode 100644 site/assets/javascripts/lunr/min/lunr.multi.min.js delete mode 100644 site/assets/javascripts/lunr/min/lunr.nl.min.js delete mode 100644 site/assets/javascripts/lunr/min/lunr.no.min.js delete mode 100644 site/assets/javascripts/lunr/min/lunr.pt.min.js delete mode 100644 site/assets/javascripts/lunr/min/lunr.ro.min.js delete mode 100644 site/assets/javascripts/lunr/min/lunr.ru.min.js delete mode 100644 site/assets/javascripts/lunr/min/lunr.sa.min.js delete mode 100644 site/assets/javascripts/lunr/min/lunr.stemmer.support.min.js delete mode 100644 site/assets/javascripts/lunr/min/lunr.sv.min.js delete mode 100644 site/assets/javascripts/lunr/min/lunr.ta.min.js delete mode 100644 site/assets/javascripts/lunr/min/lunr.te.min.js delete mode 100644 site/assets/javascripts/lunr/min/lunr.th.min.js delete mode 100644 site/assets/javascripts/lunr/min/lunr.tr.min.js delete mode 100644 site/assets/javascripts/lunr/min/lunr.vi.min.js delete mode 100644 site/assets/javascripts/lunr/min/lunr.zh.min.js delete mode 100644 site/assets/javascripts/lunr/tinyseg.js delete mode 100644 site/assets/javascripts/lunr/wordcut.js delete mode 100644 site/assets/javascripts/workers/search.2c215733.min.js delete mode 100644 site/assets/javascripts/workers/search.2c215733.min.js.map delete mode 100644 site/assets/stylesheets/main.484c7ddc.min.css delete mode 100644 site/assets/stylesheets/main.484c7ddc.min.css.map delete mode 100644 site/assets/stylesheets/palette.ab4e12ef.min.css delete mode 100644 site/assets/stylesheets/palette.ab4e12ef.min.css.map delete mode 100644 site/contributing/index.html delete mode 100644 site/index.html delete mode 100644 site/meshcore/antennas/index.html delete mode 100644 site/meshcore/companions/index.html delete mode 100644 site/meshcore/faq/index.html delete mode 100644 site/meshcore/intro/index.html delete mode 100644 site/meshcore/repeater-builds/index.html delete mode 100644 site/meshcore/repeaters/index.html delete mode 100644 site/provinces/alberta/index.html delete mode 100644 site/provinces/british-columbia/index.html delete mode 100644 site/provinces/manitoba/index.html delete mode 100644 site/provinces/new-brunswick/index.html delete mode 100644 site/provinces/newfoundland-and-labrador/index.html delete mode 100644 site/provinces/nova-scotia/index.html delete mode 100644 site/provinces/ontario/index.html delete mode 100644 site/provinces/prince-edward-island/index.html delete mode 100644 site/provinces/quebec/index.html delete mode 100644 site/provinces/saskatchewan/index.html delete mode 100644 site/provinces/territories/index.html delete mode 100644 site/resources/getting-started/index.html delete mode 100644 site/resources/glossary/index.html delete mode 100644 site/resources/links/index.html delete mode 100644 site/search/search_index.json delete mode 100644 site/sitemap.xml delete mode 100644 site/sitemap.xml.gz create mode 100644 tests/browser/accessibility.spec.mjs create mode 100644 tests/browser/critical-journeys.spec.mjs create mode 100644 tests/content/analyzer-journey.test.mjs create mode 100644 tests/content/community-directory.test.mjs create mode 100644 tests/content/configurator-ui.test.mjs create mode 100644 tests/content/devices-builds.test.mjs create mode 100644 tests/content/learn-content.test.mjs create mode 100644 tests/content/p0-content-safety.test.mjs create mode 100644 tests/content/site-postprocess.test.mjs create mode 100644 tests/content/start-journeys.test.mjs create mode 100644 tests/content/test_validate_content.py create mode 100644 tests/editor/editor-containment.test.mjs create mode 100644 tests/editor/editor-ux.test.mjs create mode 100644 ux-overhaul/DECISIONS.md create mode 100644 ux-overhaul/IMPLEMENTATION-PLAN.md create mode 100644 ux-overhaul/ROUTE-MIGRATION.md create mode 100644 ux-overhaul/STATUS.md diff --git a/.github/workflows/apply-approved-boundary.yml b/.github/workflows/apply-approved-boundary.yml index 09c10ea..cdf570b 100644 --- a/.github/workflows/apply-approved-boundary.yml +++ b/.github/workflows/apply-approved-boundary.yml @@ -122,7 +122,7 @@ jobs: shell: bash run: | set -euo pipefail - node scripts/validate-regions.js + node scripts/validate-regions.cjs python scripts/verify-region-geometry.py python scripts/verify-region-geometry.py \ --partition docs/assets/regions/canada-region-partition-digital.geojson diff --git a/.github/workflows/deploy-reviewed-site.yml b/.github/workflows/deploy-reviewed-site.yml new file mode 100644 index 0000000..e81a435 --- /dev/null +++ b/.github/workflows/deploy-reviewed-site.yml @@ -0,0 +1,57 @@ +name: Deploy reviewed site + +on: + workflow_run: + workflows: ["Validate site quality"] + types: [completed] + branches: [main] + +permissions: + contents: write + +concurrency: + group: meshcore-ca-pages + cancel-in-progress: false + +jobs: + deploy: + if: >- + github.event.workflow_run.conclusion == 'success' && + github.event.workflow_run.event == 'push' + runs-on: ubuntu-latest + timeout-minutes: 20 + + steps: + - name: Checkout validated revision + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + ref: ${{ github.event.workflow_run.head_sha }} + + - name: Set up Python + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 + with: + python-version: "3.13" + cache: pip + cache-dependency-path: requirements-docs.txt + + - name: Set up Node + uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6 + with: + node-version: "22" + cache: npm + + - name: Build the validated revision + env: + MCC_SITE_REVISION: ${{ github.event.workflow_run.head_sha }} + run: | + python -m pip install -r requirements-docs.txt + npm ci + npm run docs:build + node scripts/check-built-links.mjs + + - name: Deploy to GitHub Pages + uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./.tmp/site + cname: meshcore.ca diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml deleted file mode 100644 index 90dfc23..0000000 --- a/.github/workflows/deploy.yml +++ /dev/null @@ -1,72 +0,0 @@ -name: Deploy MkDocs site - -on: - push: - branches: [ main ] # change to master if your branch is named master - release: - types: [ published ] # redeploy site when new firmware is released - workflow_dispatch: - -permissions: - contents: write # needed so the action can push to gh-pages - -jobs: - deploy: - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - - name: Set up Python - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 - with: - python-version: "3.13" - - - name: Install MkDocs + Material - run: | - pip install mkdocs-material - - - name: Install Mkdocs-plugin - run: | - pip install mkdocs-macros-plugin pyyaml mkdocs-glightbox pymdown-extensions shapely==2.0.6 mkdocs-redirects==1.2.2 - - - name: Install proposal gateway dependencies - run: python -m pip install -r tools/region-proposal-gateway/requirements.txt - - - name: Validate MeshCore Canada proposal service - shell: bash - run: | - python -m unittest discover -s tools/region-proposal-gateway/tests -v - node --test tests/editor/*.test.mjs - node --check docs/config/editor/app.js - node --check docs/config/editor/issue.js - node --check docs/javascripts/community-submission.js - node --check docs/javascripts/submission-form.js - if git grep -n -E 'canadaverse\.org|regions-api\.meshcore\.ca|/api/meshcore-regions/proposals|192\.168\.0\.111|/home/neonx|splashpage' -- \ - docs/config/editor docs/javascripts docs/submit-idea.md \ - tools/region-proposal-gateway instructions.md; then - echo 'Production submission files must use only the approved MeshCore Canada endpoint.' >&2 - exit 1 - fi - - - name: Validate exclusive national partition - run: node scripts/validate-regions.js - - - name: Verify generated geometry - run: | - python scripts/verify-region-geometry.py - python scripts/verify-region-geometry.py --partition docs/assets/regions/canada-region-partition-digital.geojson - - - name: Validate community submission helper - run: python scripts/validate_community_submission.py - - - name: Build MkDocs site - run: mkdocs build --strict - - - name: Deploy to GitHub Pages - uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./site - cname: meshcore.ca diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml new file mode 100644 index 0000000..df55a2f --- /dev/null +++ b/.github/workflows/quality.yml @@ -0,0 +1,156 @@ +name: Validate site quality + +on: + pull_request: + push: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: site-quality-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + validate: + runs-on: ubuntu-latest + timeout-minutes: 35 + + steps: + - name: Checkout repository + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + + - name: Set up Python + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 + with: + python-version: "3.13" + cache: pip + cache-dependency-path: | + requirements-docs.txt + scripts/requirements-regions.txt + tools/region-proposal-gateway/requirements.txt + + - name: Set up Node + uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6 + with: + node-version: "22" + cache: npm + + - name: Install locked dependencies + run: | + python -m pip install -r requirements-docs.txt + python -m pip install -r scripts/requirements-regions.txt + python -m pip install -r tools/region-proposal-gateway/requirements.txt + npm ci + + - name: Enforce source and production boundaries + shell: bash + run: | + set -euo pipefail + test -z "$(git ls-files site)" + if git grep -n -E 'canadaverse\.org|regions-api\.meshcore\.ca|/api/meshcore-regions/proposals|192\.168\.0\.(24|111)|/home/neonx|splashpage' -- \ + docs/config/editor docs/javascripts docs/submit-idea.md \ + tools/region-proposal-gateway instructions.md; then + echo 'Production submission files contain staging infrastructure.' >&2 + exit 1 + fi + + - name: Validate content and generated directories + run: | + python scripts/validate-content.py + python scripts/validate-communities.py + python scripts/validate_community_submission.py + python -m unittest discover -s tests/content -p "test_*.py" -v + npm run test:content + + - name: Validate region authority and proposal automation + run: | + node scripts/validate-regions.cjs + python scripts/verify-region-geometry.py + python scripts/verify-region-geometry.py --partition docs/assets/regions/canada-region-partition-digital.geojson + python -m unittest discover -s tools/region-proposal-gateway/tests -v + python -m unittest discover -s tests/automation -v + npm run test:editor + + - name: Check browser JavaScript syntax + shell: bash + run: | + set -euo pipefail + for file in docs/config/editor/*.js docs/assets/javascripts/*.js docs/assets/regions/modules/*.js docs/assets/regions/regions.js; do + node --check "$file" + done + + - name: Build and audit local links + run: npm run check:links + + - name: Check patch hygiene + run: git diff --check + + browser: + needs: validate + runs-on: ubuntu-latest + timeout-minutes: 35 + + steps: + - name: Checkout repository + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + + - name: Set up Python + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 + with: + python-version: "3.13" + cache: pip + cache-dependency-path: requirements-docs.txt + + - name: Set up Node + uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6 + with: + node-version: "22" + cache: npm + + - name: Install site and browser dependencies + run: | + python -m pip install -r requirements-docs.txt + npm ci + npx playwright install --with-deps chromium firefox webkit + + - name: Build site + run: npm run docs:build + + - name: Run critical journeys and accessibility checks + run: npm run test:browser + + lighthouse: + needs: validate + runs-on: ubuntu-latest + timeout-minutes: 25 + + steps: + - name: Checkout repository + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + + - name: Set up Python + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 + with: + python-version: "3.13" + cache: pip + cache-dependency-path: requirements-docs.txt + + - name: Set up Node + uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6 + with: + node-version: "22" + cache: npm + + - name: Install locked dependencies + run: | + python -m pip install -r requirements-docs.txt + npm ci + + - name: Enforce Lighthouse budgets + shell: bash + run: | + export CHROME_PATH="$(command -v google-chrome || command -v google-chrome-stable)" + npm run audit:lighthouse diff --git a/.github/workflows/validate-site.yml b/.github/workflows/validate-site.yml deleted file mode 100644 index 61bb7b8..0000000 --- a/.github/workflows/validate-site.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: Validate MkDocs site - -on: - pull_request: - -permissions: - contents: read - -jobs: - validate: - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - - name: Set up Python - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 - with: - python-version: "3.x" - - - name: Install site dependencies - run: pip install mkdocs-material mkdocs-macros-plugin pyyaml mkdocs-glightbox pymdown-extensions mkdocs-redirects==1.2.2 - - - name: Validate community submission helper - run: python scripts/validate_community_submission.py - - - name: Build site - run: mkdocs build --strict diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml deleted file mode 100644 index 25cb78c..0000000 --- a/.github/workflows/validate.yml +++ /dev/null @@ -1,68 +0,0 @@ -name: Validate documentation - -on: - pull_request: - workflow_dispatch: - -permissions: - contents: read - -jobs: - validate: - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - - name: Set up Python - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 - with: - python-version: "3.13" - - - name: Validate anonymous proposal gateway - run: | - python -m pip install -r tools/region-proposal-gateway/requirements.txt - python -m pip install -r scripts/requirements-regions.txt - python -m unittest discover -s tools/region-proposal-gateway/tests -v - python -m unittest discover -s tests/automation -v - - - name: Enforce MeshCore Canada production ownership - shell: bash - run: | - if git grep -n -E 'canadaverse\.org|regions-api\.meshcore\.ca|/api/meshcore-regions/proposals|192\.168\.0\.111|/home/neonx|splashpage' -- \ - docs/config/editor docs/javascripts docs/submit-idea.md \ - tools/region-proposal-gateway instructions.md; then - echo 'Production submission files must use only the approved MeshCore Canada endpoint.' >&2 - exit 1 - fi - - - name: Install documentation dependencies - run: pip install mkdocs-material mkdocs-macros-plugin pyyaml mkdocs-glightbox pymdown-extensions shapely==2.0.6 mkdocs-redirects==1.2.2 - - - name: Validate exclusive national partition - run: node scripts/validate-regions.js - - - name: Verify generated geometry - run: | - python scripts/verify-region-geometry.py - python scripts/verify-region-geometry.py --partition docs/assets/regions/canada-region-partition-digital.geojson - - - name: Set up Node - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6 - with: - node-version: "22" - - - name: Validate region editor - run: | - node --test tests/editor/*.test.mjs - node --check docs/config/editor/app.js - node --check docs/config/editor/issue.js - node --check docs/javascripts/community-submission.js - node --check docs/javascripts/submission-form.js - - - name: Validate community submission helper - run: python scripts/validate_community_submission.py - - - name: Build documentation - run: mkdocs build --strict diff --git a/.gitignore b/.gitignore index 4c3e0f5..49c115f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,10 @@ *.swp __pycache__/ *.pyc +site/ docs/assets/regions/*.raw.geojson +node_modules/ +.tmp/ +playwright-report/ +test-results/ +lighthouse-report/ diff --git a/data/communities.json b/data/communities.json new file mode 100644 index 0000000..e6a3a50 --- /dev/null +++ b/data/communities.json @@ -0,0 +1,804 @@ +{ + "$schema": "../schemas/community-directory.schema.json", + "schema": "meshcore-canada-communities/v1", + "metadata": { + "owner": "directory-stewards", + "migrated_at": "2026-07-19", + "source_revision": "f608cfe", + "review_by": "2027-01-15", + "update_route": "../submit-idea.md" + }, + "national_defaults": { + "radio_preset": "USA/Canada (Recommended)", + "raw_radio": { + "frequency_mhz": 910.525, + "bandwidth_khz": 62.5, + "spreading_factor": 7, + "coding_rate": 5 + }, + "path_hash_mode": "3-byte", + "cli_path_setting": "set path.hash.mode 2" + }, + "directory_pages": [ + { + "slug": "british-columbia", + "title": "British Columbia", + "codes": [ + "BC" + ], + "aliases": [ + "BC" + ] + }, + { + "slug": "alberta", + "title": "Alberta", + "codes": [ + "AB" + ], + "aliases": [ + "AB" + ] + }, + { + "slug": "saskatchewan", + "title": "Saskatchewan", + "codes": [ + "SK" + ], + "aliases": [ + "SK" + ] + }, + { + "slug": "manitoba", + "title": "Manitoba", + "codes": [ + "MB" + ], + "aliases": [ + "MB" + ] + }, + { + "slug": "ontario", + "title": "Ontario", + "codes": [ + "ON" + ], + "aliases": [ + "ON" + ] + }, + { + "slug": "quebec", + "title": "Quebec", + "codes": [ + "QC" + ], + "aliases": [ + "Québec", + "QC" + ] + }, + { + "slug": "new-brunswick", + "title": "New Brunswick", + "codes": [ + "NB" + ], + "aliases": [ + "NB" + ] + }, + { + "slug": "nova-scotia", + "title": "Nova Scotia", + "codes": [ + "NS" + ], + "aliases": [ + "NS" + ] + }, + { + "slug": "prince-edward-island", + "title": "Prince Edward Island", + "codes": [ + "PE" + ], + "aliases": [ + "PEI", + "PE" + ] + }, + { + "slug": "newfoundland-and-labrador", + "title": "Newfoundland and Labrador", + "codes": [ + "NL" + ], + "aliases": [ + "Newfoundland", + "Labrador", + "NL" + ] + }, + { + "slug": "territories", + "title": "the territories", + "codes": [ + "YT", + "NT", + "NU" + ], + "aliases": [ + "Yukon", + "Northwest Territories", + "Nunavut", + "YT", + "NT", + "NU" + ] + } + ], + "province_contacts": [ + { + "id": "alberta-meshcore-canada-telegram", + "province": "AB", + "type": "telegram", + "label": "Alberta topic in MeshCore Canada", + "url": "https://t.me/MeshCoreCAN", + "health": "needs-review", + "last_checked": null + } + ], + "communities": [ + { + "id": "salish-mesh", + "name": "Salish Mesh", + "province": "BC", + "service_area": "Salish Sea and surrounding area", + "places": [ + "Salish Sea" + ], + "aliases": [ + "Salish Mesh" + ], + "status": "active", + "languages": [], + "location": { + "latitude": null, + "longitude": null, + "precision": "service-area" + }, + "settings": { + "inherit_national": true, + "overrides": {} + }, + "contacts": [ + { + "type": "website", + "label": "Salish Mesh website", + "url": "https://salishmesh.net/", + "health": "needs-review", + "last_checked": null + } + ], + "owner": null, + "verified_at": null, + "verify_by": null, + "canonical_route": "/provinces/british-columbia/#community-salish-mesh" + }, + { + "id": "calgary-area-meshcore", + "name": "Calgary and Area MeshCore", + "province": "AB", + "service_area": "Calgary and area", + "places": [ + "Calgary" + ], + "aliases": [ + "Calgary and area", + "YYC" + ], + "status": "active", + "languages": [], + "location": { + "latitude": null, + "longitude": null, + "precision": "service-area" + }, + "settings": { + "inherit_national": true, + "overrides": {} + }, + "contacts": [ + { + "type": "telegram", + "label": "Calgary topic in Mesh Alberta", + "url": "https://t.me/meshtAlta", + "health": "needs-review", + "last_checked": null + }, + { + "type": "discord", + "label": "Canada - Calgary, Alberta & Area regional channel in the MeshCore Discord", + "url": null, + "health": "needs-review", + "last_checked": null + } + ], + "owner": null, + "verified_at": null, + "verify_by": null, + "canonical_route": "/provinces/alberta/#community-calgary-area-meshcore" + }, + { + "id": "yqlmesh", + "name": "YQLMesh", + "province": "AB", + "service_area": "Lethbridge", + "places": [ + "Lethbridge" + ], + "aliases": [ + "YQL" + ], + "status": "active", + "languages": [], + "location": { + "latitude": null, + "longitude": null, + "precision": "service-area" + }, + "settings": { + "inherit_national": true, + "overrides": {} + }, + "contacts": [ + { + "type": "website", + "label": "YQLMesh website", + "url": "https://www.yqlmesh.com", + "health": "needs-review", + "last_checked": null + }, + { + "type": "facebook", + "label": "YQLMesh Community", + "url": "https://www.facebook.com/groups/839542635605360", + "health": "needs-review", + "last_checked": null + }, + { + "type": "discord", + "label": "YQLMesh Discord", + "url": "https://discord.gg/89DUBvmvu2", + "health": "needs-review", + "last_checked": null + } + ], + "owner": null, + "verified_at": null, + "verify_by": null, + "canonical_route": "/provinces/alberta/#community-yqlmesh" + }, + { + "id": "southern-alberta", + "name": "Southern Alberta", + "province": "AB", + "service_area": "Southern Alberta, including Cardston, Magrath, Raymond, and nearby areas", + "places": [ + "Cardston", + "Magrath", + "Raymond" + ], + "aliases": [ + "Southern Alberta" + ], + "status": "active", + "languages": [], + "location": { + "latitude": null, + "longitude": null, + "precision": "service-area" + }, + "settings": { + "inherit_national": true, + "overrides": {} + }, + "contacts": [ + { + "type": "discord", + "label": "Canada - Southern Alberta regional channel in the MeshCore Discord", + "url": null, + "health": "needs-review", + "last_checked": null + }, + { + "type": "telegram", + "label": "Cardston topic in Mesh Alberta", + "url": "https://t.me/meshtAlta", + "health": "needs-review", + "last_checked": null + } + ], + "owner": null, + "verified_at": null, + "verify_by": null, + "canonical_route": "/provinces/alberta/#community-southern-alberta" + }, + { + "id": "yyc-meshcore-discord", + "name": "YYC MeshCore Discord Group", + "province": "AB", + "service_area": "Calgary", + "places": [ + "Calgary" + ], + "aliases": [ + "YYC" + ], + "status": "active", + "languages": [], + "location": { + "latitude": null, + "longitude": null, + "precision": "service-area" + }, + "settings": { + "inherit_national": true, + "overrides": {} + }, + "contacts": [ + { + "type": "discord", + "label": "YYC MeshCore Discord", + "url": "https://discord.gg/CznDhsRWnJ", + "health": "needs-review", + "last_checked": null + }, + { + "type": "website", + "label": "MeshMonitoring", + "url": "https://meshmonitoring.com/", + "health": "needs-review", + "last_checked": null + } + ], + "owner": null, + "verified_at": null, + "verify_by": null, + "canonical_route": "/provinces/alberta/#community-yyc-meshcore-discord" + }, + { + "id": "stoonmesh", + "name": "StoonMesh", + "province": "SK", + "service_area": "Central Saskatchewan", + "places": [], + "aliases": [ + "Central Saskatchewan" + ], + "status": "active", + "languages": [], + "location": { + "latitude": null, + "longitude": null, + "precision": "service-area" + }, + "settings": { + "inherit_national": true, + "overrides": { + "path_hash_mode": "1-byte" + } + }, + "contacts": [ + { + "type": "discord", + "label": "StoonMesh Discord", + "url": "https://discord.gg/7yGnJuMGkG", + "health": "needs-review", + "last_checked": null + } + ], + "owner": null, + "verified_at": null, + "verify_by": null, + "canonical_route": "/provinces/saskatchewan/#community-stoonmesh" + }, + { + "id": "greater-ottawa-mesh-enthusiasts", + "name": "Greater Ottawa Mesh Enthusiasts", + "province": "ON", + "service_area": "Eastern Ontario and Western Quebec", + "places": [ + "Ottawa" + ], + "aliases": [ + "Eastern Ontario", + "Western Quebec" + ], + "status": "active", + "languages": [], + "location": { + "latitude": null, + "longitude": null, + "precision": "service-area" + }, + "settings": { + "inherit_national": true, + "overrides": {} + }, + "contacts": [ + { + "type": "discord", + "label": "Greater Ottawa Mesh Enthusiasts Discord", + "url": "https://discord.gg/WSyNd8SfNr", + "health": "needs-review", + "last_checked": null + }, + { + "type": "website", + "label": "Ottawa Mesh website", + "url": "https://ottawamesh.ca/", + "health": "needs-review", + "last_checked": null + } + ], + "owner": null, + "verified_at": null, + "verify_by": null, + "canonical_route": "/provinces/ontario/#community-greater-ottawa-mesh-enthusiasts" + }, + { + "id": "gta-lora-meshes", + "name": "GTA+-Lora-Meshes", + "province": "ON", + "service_area": "Southern Ontario", + "places": [], + "aliases": [ + "GTA", + "Southern Ontario" + ], + "status": "active", + "languages": [], + "location": { + "latitude": null, + "longitude": null, + "precision": "service-area" + }, + "settings": { + "inherit_national": true, + "overrides": {} + }, + "contacts": [ + { + "type": "discord", + "label": "GTA+-Lora-Meshes Discord", + "url": "https://discord.gg/wSHbeb86r4", + "health": "needs-review", + "last_checked": null + } + ], + "owner": null, + "verified_at": null, + "verify_by": null, + "canonical_route": "/provinces/ontario/#community-gta-lora-meshes" + }, + { + "id": "quinte-mesh-network", + "name": "Quinte Mesh Network", + "province": "ON", + "service_area": "Quinte Region, including Belleville, Trenton, Prince Edward County, and nearby areas", + "places": [ + "Belleville", + "Trenton", + "Prince Edward County" + ], + "aliases": [ + "Quinte Region" + ], + "status": "active", + "languages": [], + "location": { + "latitude": null, + "longitude": null, + "precision": "service-area" + }, + "settings": { + "inherit_national": true, + "overrides": {} + }, + "contacts": [ + { + "type": "discord", + "label": "Quinte Mesh Network Discord", + "url": "https://discord.gg/V5esJEP67X", + "health": "needs-review", + "last_checked": null + }, + { + "type": "website", + "label": "Quinte Mesh Network website", + "url": "https://quintemesh.ca/", + "health": "needs-review", + "last_checked": null + } + ], + "owner": null, + "verified_at": null, + "verify_by": null, + "canonical_route": "/provinces/ontario/#community-quinte-mesh-network" + }, + { + "id": "mesh-quebec", + "name": "Mesh Quebec", + "province": "QC", + "service_area": "Quebec", + "places": [], + "aliases": [ + "Québec" + ], + "status": "active", + "languages": [], + "location": { + "latitude": null, + "longitude": null, + "precision": "service-area" + }, + "settings": { + "inherit_national": true, + "overrides": {} + }, + "contacts": [ + { + "type": "website", + "label": "Mesh Quebec website", + "url": "https://qcmesh.net", + "health": "needs-review", + "last_checked": null + } + ], + "owner": null, + "verified_at": null, + "verify_by": null, + "canonical_route": "/provinces/quebec/#community-mesh-quebec" + }, + { + "id": "montreal-mesh", + "name": "Montreal Mesh", + "province": "QC", + "service_area": "Greater Montreal", + "places": [ + "Montreal" + ], + "aliases": [ + "Greater Montreal", + "Montréal" + ], + "status": "active", + "languages": [], + "location": { + "latitude": null, + "longitude": null, + "precision": "service-area" + }, + "settings": { + "inherit_national": true, + "overrides": {} + }, + "contacts": [ + { + "type": "website", + "label": "Montreal Mesh website", + "url": "https://www.montrealmesh.ca", + "health": "needs-review", + "last_checked": null + } + ], + "owner": null, + "verified_at": null, + "verify_by": null, + "canonical_route": "/provinces/quebec/#community-montreal-mesh" + }, + { + "id": "reseau-mesh-capitale-yqb", + "name": "Réseau Mesh de la Capitale YQB", + "province": "QC", + "service_area": "Quebec City", + "places": [ + "Quebec City" + ], + "aliases": [ + "YQB", + "Capitale" + ], + "status": "active", + "languages": [], + "location": { + "latitude": null, + "longitude": null, + "precision": "service-area" + }, + "settings": { + "inherit_national": true, + "overrides": {} + }, + "contacts": [ + { + "type": "discord", + "label": "Réseau Mesh de la Capitale YQB Discord", + "url": "https://discord.gg/UhGjTF2MfA", + "health": "needs-review", + "last_checked": null + } + ], + "owner": null, + "verified_at": null, + "verify_by": null, + "canonical_route": "/provinces/quebec/#community-reseau-mesh-capitale-yqb" + }, + { + "id": "reseau-mesh-saguenay-lac-saint-jean-ytf", + "name": "Réseau Mesh du Saguenay Lac st-Jean YTF", + "province": "QC", + "service_area": "Saguenay–Lac-Saint-Jean (YTF)", + "places": [ + "Saguenay", + "Lac-Saint-Jean" + ], + "aliases": [ + "YTF", + "Saguenay Lac st-Jean" + ], + "status": "active", + "languages": [], + "location": { + "latitude": null, + "longitude": null, + "precision": "service-area" + }, + "settings": { + "inherit_national": true, + "overrides": {} + }, + "contacts": [ + { + "type": "discord", + "label": "Réseau Mesh du Saguenay Lac st-Jean YTF Discord", + "url": "https://discord.gg/wUR394yXt", + "health": "needs-review", + "last_checked": null + }, + { + "type": "facebook", + "label": "Réseau Mesh du Saguenay Lac st-Jean YTF Facebook", + "url": "https://www.facebook.com/share/g/1GjkHAyZAM/", + "health": "needs-review", + "last_checked": null + }, + { + "type": "meshmapper", + "label": "Réseau Mesh du Saguenay Lac st-Jean YTF MeshMapper", + "url": "https://ytf.meshmapper.net/", + "health": "needs-review", + "last_checked": null + } + ], + "owner": null, + "verified_at": null, + "verify_by": null, + "canonical_route": "/provinces/quebec/#community-reseau-mesh-saguenay-lac-saint-jean-ytf" + }, + { + "id": "reseau-libre", + "name": "Réseau Libre", + "province": "QC", + "service_area": "Montreal", + "places": [ + "Montreal" + ], + "aliases": [ + "Montréal" + ], + "status": "active", + "languages": [], + "location": { + "latitude": null, + "longitude": null, + "precision": "service-area" + }, + "settings": { + "inherit_national": true, + "overrides": {} + }, + "contacts": [ + { + "type": "website", + "label": "Réseau Libre website", + "url": "https://lora.reseaulibre.ca/", + "health": "needs-review", + "last_checked": null + } + ], + "owner": null, + "verified_at": null, + "verify_by": null, + "canonical_route": "/provinces/quebec/#community-reseau-libre" + }, + { + "id": "southern-new-brunswick", + "name": "Southern New Brunswick", + "province": "NB", + "service_area": "Fredericton, Saint John, Moncton, and nearby areas", + "places": [ + "Fredericton", + "Saint John", + "Moncton" + ], + "aliases": [ + "Southern New Brunswick" + ], + "status": "forming", + "languages": [], + "location": { + "latitude": null, + "longitude": null, + "precision": "service-area" + }, + "settings": { + "inherit_national": true, + "overrides": {} + }, + "contacts": [ + { + "type": "facebook", + "label": "MESHCORE Saint John, N.B.", + "url": "https://www.facebook.com/groups/613466831163684", + "health": "needs-review", + "last_checked": null + } + ], + "owner": null, + "verified_at": null, + "verify_by": null, + "canonical_route": "/provinces/new-brunswick/#community-southern-new-brunswick" + }, + { + "id": "lunenburg-county-mesh", + "name": "Lunenburg County Mesh", + "province": "NS", + "service_area": "Lunenburg County", + "places": [ + "Lunenburg County" + ], + "aliases": [], + "status": "active", + "languages": [], + "location": { + "latitude": null, + "longitude": null, + "precision": "service-area" + }, + "settings": { + "inherit_national": true, + "overrides": {} + }, + "contacts": [ + { + "type": "website", + "label": "Lunenburg County Mesh website", + "url": "http://www.lunenburgcountymesh.ca", + "health": "needs-review", + "last_checked": null + } + ], + "owner": null, + "verified_at": null, + "verify_by": null, + "canonical_route": "/provinces/nova-scotia/#community-lunenburg-county-mesh" + } + ] +} diff --git a/docs/analyzer/broker-reference.md b/docs/analyzer/broker-reference.md index 4278fb5..40bd3cd 100644 --- a/docs/analyzer/broker-reference.md +++ b/docs/analyzer/broker-reference.md @@ -1,50 +1,87 @@ -# Broker Reference +--- +title: Advanced observer endpoint reference +description: Inspect the canonical MeshCore Canada endpoint, transport, authentication, topic, and packet-mode values. +audience: + - observer-operators + - service-operators +task: reference-observer-endpoints +scope: canada-baseline +status: draft +owner: meshcore-canada +last_reviewed: 2026-07-19 +review_by: 2026-10-19 +difficulty: advanced +estimated_time: 8 minutes +destructive: false +page_styles: + - assets/styles/analyzer.css +page_scripts: + - assets/javascripts/analyzer-broker-reference.js +--- -All MeshCore.ca observer paths publish to the same redundant broker pair. +# Advanced observer endpoint reference -| Broker | Host | Port | Transport | TLS | Token audience | -|--------|------|------|-----------|-----|----------------| -| Primary | `mqtt1.meshcore.ca` | `443` | WebSockets | Enabled + verified | `mqtt1.meshcore.ca` | -| Backup | `mqtt2.meshcore.ca` | `443` | WebSockets | Enabled + verified | `mqtt2.meshcore.ca` | +This page is for operators who already chose an [observer method](intro.md). Beginners should follow the method guide instead of copying values from this page. -Use JWT token authentication where the setup tool exposes it. Do not set a static MQTT password unless a path-specific guide tells you to. +## Generated endpoint table -## Topic Pattern +The table is loaded from the [canonical observer configuration](observer-config.json). -Most observer paths publish packet telemetry under this pattern: +
+
+ + + + + + + + + + + + +
UseHostPortTransportTLSToken audience
+
+

Loading canonical endpoint values…

+
-```text -meshcore/{IATA}/{PUBLIC_KEY}/packets -``` +Without JavaScript, open [observer-config.json](observer-config.json) directly. -Status topics generally use: +## Topic templates ```text +meshcore/{IATA}/{PUBLIC_KEY}/packets meshcore/{IATA}/{PUBLIC_KEY}/status ``` -`{IATA}` must be a real 3-letter airport code nearest to the observer. `{PUBLIC_KEY}` is supplied by the MeshCore node or integration. +`{IATA}` is the observer's real three-letter location code. `{PUBLIC_KEY}` is supplied by the radio or integration. Never substitute a private key. + +## Authentication and transport -## Payload Mode +- Use WebSockets on port `443`. +- Require TLS and verify certificates. +- Use the MeshCore JWT token option where available. +- Match each token audience to its endpoint host. +- Do not put a token or password into a URL, screenshot, issue, or diagnostic bundle. -MeshCore.ca packet visibility needs packet payload publishing, not only status publishing. +## Packet mode by method -| Path | Required packet setting | -|------|-------------------------| -| MQTT firmware | `set mqtt.packets on`, `set bridge.enabled on`, `set mqtt.rx on`, `set mqtt.tx advert` | -| MCtoMQTT | Broker config created by the MeshCore.ca helper | -| Companion capture | `PACKETCAPTURE_MQTT*_TOPIC_PACKETS` configured | +| Method | Required packet setting | +|---|---| +| MQTT firmware | `mqtt.packets on`, `bridge.enabled on`, and `mqtt.rx on` | +| MCtoMQTT / companion capture | Configure the `/packets` topic | | PyMC | `format: letsmesh` | | Home Assistant | **Payload Mode** = `packet`, or older **Packets (Lets Mesh)** enabled | -| RemoteTerm | Community MQTT packet topic template enabled | +| RemoteTerm | Enable the Community MQTT packet topic | -## Common Broker Mistakes +## Interpret status correctly -| Symptom | Likely cause | -|---------|--------------| -| Primary connects, backup fails | Backup token audience still set to `mqtt1.meshcore.ca` | -| Observer appears under the wrong city | IATA field is wrong or inconsistent between broker entries | -| Broker connects but no packets appear | Status is publishing, but packet payload mode is not enabled | -| Nothing appears | Radio is off the local mesh, IATA code is invalid, or outbound WSS is blocked | +| State | What it proves | +|---|---| +| DNS or port reachable | The host can reach the endpoint | +| Broker connected | Transport and authentication succeeded | +| Observer visible | Status reached the live service | +| Recent packet visible | The radio-to-viewer path works end to end | -For path-specific commands, use [Troubleshooting](troubleshooting.md). +Only the last state completes [observer verification](verify.md). diff --git a/docs/analyzer/builds/mctomqtt.md b/docs/analyzer/builds/mctomqtt.md index 9e03b13..dcced89 100644 --- a/docs/analyzer/builds/mctomqtt.md +++ b/docs/analyzer/builds/mctomqtt.md @@ -1,157 +1,198 @@ -# MCtoMQTT (USB Serial Host) +--- +title: Observe with MCtoMQTT +description: Bridge a USB radio through a Linux or macOS host with review-first changes, backup, rollback, and end-to-end checks. +audience: + - observer-operators + - service-operators +task: configure-mctomqtt-observer +scope: canada-baseline +status: draft +owner: meshcore-canada +last_reviewed: 2026-07-19 +review_by: 2026-10-19 +difficulty: advanced +estimated_time: 30 minutes +destructive: true +page_styles: + - assets/styles/analyzer.css +--- -Bridge a USB-connected MeshCore node to MQTT brokers using `meshcoretomqtt`. This runs on a Linux or macOS host with the device connected over USB serial. +# Observe with MCtoMQTT -!!! info "What it does" - The MCtoMQTT service reads packets from a MeshCore device over its serial port and publishes them to one or more MQTT brokers. A config drop-in file tells it where to send the data. +MCtoMQTT reads packets from a USB-connected MeshCore radio and publishes them from an always-on Linux or macOS host. -## Prerequisites +## Is this method right for you? -| Requirement | Details | -|-------------|---------| -| Device | MeshCore node with packet logging enabled, connected via USB serial | -| Host | Linux or macOS | -| Tools | `curl` installed | -| IATA Code | Your real 3-letter IATA airport code (e.g. `YOW` for Ottawa) | -| Mesh Settings | Connected radio uses `USA/Canada (Recommended)` and 3-byte path hashes | +
+
Choose it whenA Linux or macOS host stays beside a packet-log radio connected over USB.
+
Choose another method whenYou already operate RemoteTerm, Home Assistant, or PyMC, or want a standalone Wi-Fi board.
+
Stays onlineThe radio, USB connection, host service, and internet connection.
+
-## Quick Setup +For a companion radio connected by BLE, serial, or TCP, the same helper can configure `meshcore-packet-capture`; use the companion section below. -If `meshcoretomqtt` is already installed, run the one-liner to add the MeshCore.ca brokers: +## Before you start -```bash -bash <(curl -fsSL https://meshcore.ca/analyzer/scripts/add-meshcore-ca-broker.sh) --device serial-host -``` +| Requirement | Check | +|---|---| +| Radio | Packet logging works over USB and the local mesh settings are correct | +| Host | Linux or macOS for `meshcoretomqtt`; Windows is supported only for companion capture | +| Access | You can read the current config and restart the service | +| Location | A real three-letter [location code](../iata-codes.md) | +| Tools | `curl`, a text viewer, and `systemctl` on Linux | + +!!! warning "Review before running" + The published helpers currently follow the live site and do not have a pinned release checksum. Download and inspect them. A real `--dry-run`, published checksum, and pinned installer remain open work; `--no-restart` limits the first run but is not a dry run. + +Sources: + +- [Bash helper source](https://github.com/MeshCore-ca/MeshCore-Canada/blob/main/docs/analyzer/scripts/add-meshcore-ca-broker.sh) +- [Published Bash helper](https://meshcore.ca/analyzer/scripts/add-meshcore-ca-broker.sh) +- [PowerShell companion helper source](https://github.com/MeshCore-ca/MeshCore-Canada/blob/main/docs/analyzer/scripts/add-meshcore-ca-packetcapture-broker.ps1) -This creates a config drop-in at `/etc/mctomqtt/config.d/20-meshcore-ca.toml` pointing at the MeshCore.ca broker pair, then restarts the service. +## What this changes -### Fresh Install +For a serial host, the helper: -If `meshcoretomqtt` is not yet installed, add `--install-mctomqtt` and the script will run the upstream installer first: +- writes `/etc/mctomqtt/config.d/20-meshcore-ca.toml`; +- makes a timestamped `.bak.` copy when that file exists; +- adds the primary and backup MeshCore Canada endpoints and location code; and +- restarts `mctomqtt` unless `--no-restart` is used. + +For companion capture, it updates `~/.meshcore-packet-capture/.env.local`, writes a timestamped backup, configures slots 1 and 2, disables slots 3–6, and may restart the capture service. + +Install flags download and run separate upstream installers. Do not use them until you have reviewed the named upstream installer too. + +## Set up + +### 1. Download and inspect the helper ```bash -bash <(curl -fsSL https://meshcore.ca/analyzer/scripts/add-meshcore-ca-broker.sh) --device serial-host --install-mctomqtt +workdir="$(mktemp -d)" +curl -fsSLo "$workdir/add-meshcore-ca-broker.sh" https://meshcore.ca/analyzer/scripts/add-meshcore-ca-broker.sh +less "$workdir/add-meshcore-ca-broker.sh" ``` -!!! tip - The upstream installer will walk you through serial port selection. +Confirm the source URL, affected paths, endpoint hosts, backup behaviour, and restart behaviour. Keep the downloaded file for the rest of this setup. -## Specifying Your Region +### 2. Write without restarting -Pass your IATA code via the `--iata` flag or the `MESHCORE_CA_IATA` environment variable. Use the real 3-letter airport code nearest to you: +Replace `YOW` with the real location code nearest the observer: -=== "Flag" +```bash +bash "$workdir/add-meshcore-ca-broker.sh" --device serial-host --iata YOW --no-restart +``` - ```bash - bash <(curl -fsSL https://meshcore.ca/analyzer/scripts/add-meshcore-ca-broker.sh) --device serial-host --iata YOW - ``` +The helper validates the code shape, rejects `XXX`, warns on codes outside its quick list, and prints the changed and backup paths. -=== "Environment Variable" +### 3. Review the result - ```bash - MESHCORE_CA_IATA=YOW bash <(curl -fsSL https://meshcore.ca/analyzer/scripts/add-meshcore-ca-broker.sh) --device serial-host - ``` +```bash +sudo sed -n '1,220p' /etc/mctomqtt/config.d/20-meshcore-ca.toml +``` -If omitted, the script will prompt interactively. +Expected: -!!! warning "Use a real IATA code" - The helper shows a Canadian quick list when it prompts. If your nearest real airport code is not shown, you can still type it, but continue only if it is a real IATA airport code. Do not use `CAN` as shorthand for Canada; it is a real airport code for Guangzhou and will tag your observer to the wrong region. +- the location code is correct; +- primary host and audience are `mqtt1.meshcore.ca`; +- backup host and audience are `mqtt2.meshcore.ca`; +- port is `443`, transport is WebSockets, and TLS verification is enabled. -!!! tip "Check the radio first" - MCtoMQTT can publish packets from a connected radio, but it cannot fix a radio that is listening on the wrong mesh settings. Before troubleshooting MQTT, confirm the MeshCore node is on **USA/Canada (Recommended)** or `910.525 MHz / 62.5 kHz / SF7 / CR5`, and that companion-style radios use 3-byte path hashes. +Do not paste the full file into a public issue without reviewing it. -## What the Script Creates +### 4. Restart deliberately -The drop-in config at `/etc/mctomqtt/config.d/20-meshcore-ca.toml`: +```bash +sudo systemctl restart mctomqtt +sudo systemctl status mctomqtt --no-pager +``` -??? note "View full config" +**Expected:** the service remains active without repeated TLS or authentication errors. - ```toml - [general] - iata = "YOW" +### Optional faster command after review - [[broker]] - name = "meshcore-ca-1" - enabled = true - server = "mqtt1.meshcore.ca" - port = 443 - transport = "websockets" - keepalive = 60 - qos = 0 - retain = true +If you just reviewed the current published helper and accept that its URL is not pinned, this executes the same source directly: - [broker.tls] - enabled = true - verify = true +```bash +bash <(curl -fsSL https://meshcore.ca/analyzer/scripts/add-meshcore-ca-broker.sh) --device serial-host --iata YOW +``` - [broker.auth] - method = "token" - audience = "mqtt1.meshcore.ca" +The downloaded local-file route above is easier to audit and recover. - [[broker]] - name = "meshcore-ca-2" - enabled = true - server = "mqtt2.meshcore.ca" - port = 443 - transport = "websockets" - keepalive = 60 - qos = 0 - retain = true +### Fresh installation - [broker.tls] - enabled = true - verify = true +`--install-mctomqtt` downloads and runs the upstream `meshcoretomqtt` installer. Review the current upstream installer first, then add the flag to the downloaded helper: - [broker.auth] - method = "token" - audience = "mqtt2.meshcore.ca" - ``` +```bash +bash "$workdir/add-meshcore-ca-broker.sh" --device serial-host --iata YOW --install-mctomqtt +``` + +The upstream installer controls serial-port selection and package changes. MeshCore Canada does not pin or checksum it on this page. -## Companion Devices (BLE / Serial / TCP) +## Companion capture -For companion radios (not packet-log serial hosts), use the companion path instead: +Set the companion radio to the local mesh settings first. -Before running the companion helper, set the companion to **USA/Canada (Recommended)** and set path hash mode to **3-byte** in the companion app or config tool. +=== "Linux or macOS" -=== "Linux / macOS" + Review the same Bash helper, then run: ```bash - bash <(curl -fsSL https://meshcore.ca/analyzer/scripts/add-meshcore-ca-broker.sh) --device companion + bash "$workdir/add-meshcore-ca-broker.sh" --device companion --iata YOW --no-restart ``` - Add `--install-packetcapture` for a fresh install. The upstream installer will walk you through BLE, serial, or TCP connection setup. + Review `~/.meshcore-packet-capture/.env.local`, then restart your capture process. === "Windows PowerShell" + Download and inspect the helper before running: + ```powershell - powershell -NoProfile -ExecutionPolicy Bypass -Command "iwr https://meshcore.ca/analyzer/scripts/add-meshcore-ca-packetcapture-broker.ps1 -UseBasicParsing | iex" + $helper = Join-Path $env:TEMP "add-meshcore-ca-packetcapture-broker.ps1" + Invoke-WebRequest https://meshcore.ca/analyzer/scripts/add-meshcore-ca-packetcapture-broker.ps1 -OutFile $helper + Get-Content $helper + powershell -NoProfile -ExecutionPolicy Bypass -File $helper -Iata YOW ``` - Config is stored under `%USERPROFILE%\.meshcore-packet-capture\.env.local`. + The PowerShell helper changes `%USERPROFILE%\.meshcore-packet-capture\.env.local` and creates a backup. Its optional install switch executes an upstream installer and must be reviewed first. + +There is no documented `meshcoretomqtt` Windows installer for the packet-log serial-host path. + +## Expected result + +The service stays active, at least the primary endpoint connects, and packet activity increases when the radio hears nearby traffic. + +## Verify in CoreScope -!!! warning "Windows and Serial Hosts" - There is no upstream `meshcoretomqtt` Windows installer. Keep packet-log serial hosts on Linux or macOS. The Windows PowerShell helper is for companion radios only. +1. Confirm the service remains active. +2. Find the observer in [CoreScope Observers](https://live.meshcore.ca/#/observers). +3. Wait for normal nearby activity. +4. Confirm a recent packet in [CoreScope Packets](https://live.meshcore.ca/#/packets). -## Quick Reference +Complete [Check your observer](../verify.md). A running service or broker connection alone is not enough. -| Path | Manager | Connection | Config Location | -|------|---------|------------|-----------------| -| Serial Host | meshcoretomqtt | USB serial | `/etc/mctomqtt/config.d/20-meshcore-ca.toml` | -| Companion | meshcore-packet-capture | BLE, serial, or TCP | `~/.meshcore-packet-capture/.env.local` | +## Recovery + +Use the exact backup path printed by the helper. + +If a serial-host drop-in existed before: + +```bash +sudo cp -- '/etc/mctomqtt/config.d/20-meshcore-ca.toml.bak.' '/etc/mctomqtt/config.d/20-meshcore-ca.toml' +sudo systemctl restart mctomqtt +``` + +If the helper created a new drop-in and no prior file existed: + +```bash +sudo rm -- '/etc/mctomqtt/config.d/20-meshcore-ca.toml' +sudo systemctl restart mctomqtt +``` -## Script Options +For companion capture, restore the printed `.env.local.bak.` over `.env.local`, preserve its permissions, and restart the capture process. -| Flag | Description | -|------|-------------| -| `--iata CODE` | Real 3-letter IATA airport code | -| `--list-iata` | Show the Canadian quick-list choices | -| `--device TYPE` | `serial-host` or `companion` | -| `--install-mctomqtt` | Install meshcoretomqtt if not present | -| `--install-packetcapture` | Install meshcore-packet-capture if not present | -| `--no-restart` | Patch config without restarting the service | -| `--config-dir PATH` | Override config dir (default: `/etc/mctomqtt`) | -| `--service NAME` | Override systemd service name (default: `mctomqtt`) | +The helper does not uninstall software added by an upstream install flag. Use that upstream project's reviewed uninstall process. -## Verify +## If verification fails -Once your service is running, head to [Check Your Observer](../verify.md) to confirm it's reporting correctly. +Use [symptom-first troubleshooting](../troubleshooting.md). Share only a short redacted service status and the changed-path summary. diff --git a/docs/analyzer/builds/meshcore-ha.md b/docs/analyzer/builds/meshcore-ha.md index 934a305..fc3049f 100644 --- a/docs/analyzer/builds/meshcore-ha.md +++ b/docs/analyzer/builds/meshcore-ha.md @@ -1,95 +1,106 @@ -# MeshCore-HA (Home Assistant) +--- +title: Observe with Home Assistant +description: Add MeshCore Canada packet publishing to an existing Home Assistant MeshCore integration and verify it end to end. +audience: + - observer-operators + - home-assistant-users +task: configure-home-assistant-observer +scope: canada-baseline +status: draft +owner: meshcore-canada +last_reviewed: 2026-07-19 +review_by: 2026-10-19 +difficulty: intermediate +estimated_time: 15 minutes +destructive: false +page_styles: + - assets/styles/analyzer.css +--- -Add the MeshCore.ca broker pair to your Home Assistant MeshCore integration. This connects your HA instance to the Canadian mesh telemetry network. +# Observe with Home Assistant -## Prerequisites +Use the MeshCore integration you already run in Home Assistant to publish packet telemetry to MeshCore Canada. -| Requirement | Details | -|-------------|---------| -| Home Assistant | With the MeshCore integration installed | -| MeshCore Node | Connected to HA via USB, BLE, or TCP | -| IATA Code | The same real 3-letter IATA airport code used by your observer (e.g. `YOW` for Ottawa) | -| Mesh Settings | Connected node uses `USA/Canada (Recommended)` and 3-byte path hashes | +## Is this method right for you? -## Setup +
+
Choose it whenHome Assistant already has a working MeshCore integration and connected radio.
+
Choose another method whenYou would install Home Assistant only for observing.
+
Stays onlineHome Assistant, the radio connection, and internet access.
+
-!!! tip "Known-good setup" - A Home Assistant observer needs three things to line up: both brokers connected, **Payload Mode** set to `packet` (or **Packets (Lets Mesh)** enabled on older screens), and the same real IATA code on both broker entries. +## Version and screen check - The connected MeshCore node must also be on the MeshCore Canada network settings: **USA/Canada (Recommended)**, or raw radio values `910.525 MHz / 62.5 kHz / SF7 / CR5`, with 3-byte path hashes. +This guide is not pinned to a tested integration release. Identify the interface before changing it: -### 1. Open Broker Settings +| Interface you see | Packet control | Location control | +|---|---|---| +| Current-style screen | **Payload Mode** = `packet` | Free-text **Broker IATA Code** | +| Older screen | **Packets (Lets Mesh)** enabled | May use a limited picker | -Navigate to: +If your screen does not accept the correct location code, update the MeshCore integration. Do not substitute a wrong nearby code. -**Settings** > **Devices & Services** > **MeshCore** > **Configure** > **Manage MQTT Brokers** +## Before you start -### 2. Configure the Primary Broker +- [ ] Home Assistant and the MeshCore integration are healthy. +- [ ] The radio is connected over a supported USB, BLE, or TCP path. +- [ ] The radio uses the local mesh settings. +- [ ] You chose a real [location code](../iata-codes.md). +- [ ] You read [Observer data, access, and privacy](../data-collection-access.md). + +## What this changes + +You add two MQTT broker entries inside the MeshCore integration and enable packet payloads. No Wi-Fi password or static MQTT password belongs in these entries. + +## Set up + +Open: + +**Settings** → **Devices & Services** → **MeshCore** → **Configure** → **Manage MQTT Brokers** + +Add the primary entry: | Field | Value | -|-------|-------| +|---|---| | Server | `mqtt1.meshcore.ca` | | Port | `443` | | Transport | `websockets` | -| Use TLS | :material-check: Enabled | -| TLS Verify | :material-check: Enabled | +| Use TLS | Enabled | +| TLS Verify | Enabled | | Username / Password | Leave blank | -| Use MeshCore Auth Token | :material-check: Enabled | +| Use MeshCore Auth Token | Enabled | | Token Audience | `mqtt1.meshcore.ca` | -| Auth Token TTL | Leave default | | Payload Mode | `packet` | | Status Topic | `meshcore/{IATA}/{PUBLIC_KEY}/status` | | Packets Topic | `meshcore/{IATA}/{PUBLIC_KEY}/packets` | -| Client ID Prefix | Optional | -| Owner Public Key | Optional (TLS verified only) | -| Owner Email | Optional (TLS verified only) | -### 3. Configure the Backup Broker +Set the integration's location field to the real three-letter code nearest the observer. -Add a second broker with the same settings, changing only: +Add the backup entry with the same settings, changing only: | Field | Value | -|-------|-------| +|---|---| | Server | `mqtt2.meshcore.ca` | | Token Audience | `mqtt2.meshcore.ca` | -All other fields remain the same as the primary broker. - -!!! warning "Packet mode is required" - If your Home Assistant version shows a checkbox or option named **Packets (Lets Mesh)**, enable it. In newer versions this is the **Payload Mode** setting and it must be `packet`. If this is missed, the broker can appear connected while no packet data shows up. - -### 4. Set Your Region - -Make sure your IATA code is set in the integration. The `{IATA}` placeholder in the topic templates will be replaced with your code (e.g. `YOW`). +Leave optional owner fields blank unless they are needed. Save both entries. -The Home Assistant integration must use the same IATA code as the observer that is publishing packets. If Home Assistant is set to `YOW` but the observer publishes under `YKF`, Home Assistant will subscribe to the wrong topics and look empty. +## Expected result -Current MeshCore-HA versions use a free-text **Broker IATA Code** field, so you can type a real code such as `YTR` even if it is missing from a quick list. If your Home Assistant screen only offers a picker and does not let you enter the code manually, update the MeshCore integration before using a neighboring airport code as a workaround. +Both entries show connected. Normal nearby radio activity produces packet activity in the integration. A connected broker without packet activity usually means packet mode is off or the radio hears nothing. -!!! tip - You can find common Canadian IATA codes on the [IATA Region Codes](../iata-codes.md) page. If your nearest real airport code is missing from that quick list, you can still use it. +## Verify in CoreScope -## Common HA Symptoms +1. Find the observer in [CoreScope Observers](https://live.meshcore.ca/#/observers). +2. Wait for normal nearby MeshCore activity. +3. Confirm a recent packet in [CoreScope Packets](https://live.meshcore.ca/#/packets). -| Symptom | Most likely fix | -|---------|-----------------| -| Brokers show connected but no packets appear | Enable **Packets (Lets Mesh)** or set **Payload Mode** to `packet` | -| `YTR` or another code is not selectable | Update MeshCore-HA, then type the code in **Broker IATA Code** | -| One broker works and the backup does not | Check the second broker's **Token Audience** is `mqtt2.meshcore.ca` | -| Packets appear under the wrong city | Set both broker IATA fields to the nearest real airport code | +Complete [Check your observer](../verify.md). Do not treat Home Assistant's connected badge as final proof. -## Quick Reference +## Recovery -| Setting | Primary | Backup | -|---------|---------|--------| -| Server | `mqtt1.meshcore.ca` | `mqtt2.meshcore.ca` | -| Port | `443` | `443` | -| Transport | `websockets` | `websockets` | -| TLS | Enabled + Verified | Enabled + Verified | -| Auth | MeshCore JWT Token | MeshCore JWT Token | -| Audience | `mqtt1.meshcore.ca` | `mqtt2.meshcore.ca` | -| Payload Mode | `packet` | `packet` | +Disable or remove only the two MeshCore Canada broker entries you added. Keep unrelated Home Assistant integrations and the radio connection unchanged. Confirm the original MeshCore integration still operates. -## Verify +## If verification fails -After saving, head to [Check Your Observer](../verify.md) to confirm it's reporting correctly. +Use [symptom-first troubleshooting](../troubleshooting.md). Record the Home Assistant version, MeshCore integration version, first failed stage, and a redacted error—never a full diagnostics archive without review. diff --git a/docs/analyzer/builds/mqtt-firmware.md b/docs/analyzer/builds/mqtt-firmware.md index ccb715e..8b279ef 100644 --- a/docs/analyzer/builds/mqtt-firmware.md +++ b/docs/analyzer/builds/mqtt-firmware.md @@ -1,242 +1,165 @@ -# MQTT Firmware (Standalone Observer) +--- +title: Build a standalone MQTT observer +description: Flash a supported Wi-Fi LoRa board, build safe local CLI commands, recover if needed, and verify packets in CoreScope. +audience: + - observer-operators +task: configure-standalone-observer +scope: experimental +status: draft +owner: meshcore-canada +last_reviewed: 2026-07-19 +review_by: 2026-10-19 +difficulty: advanced +estimated_time: 35 minutes +destructive: true +page_styles: + - assets/styles/analyzer.css +page_scripts: + - assets/javascripts/analyzer-command-builder.js +--- + +# Build a standalone MQTT observer + +Dedicated observer firmware lets a supported Wi-Fi LoRa board send nearby packet telemetry without a separate host computer. + +## Is this method right for you? + +
+
Choose it whenA supported Wi-Fi LoRa board can be dedicated to observing.
+
Choose another method whenThe radio is already managed by RemoteTerm, Home Assistant, PyMC, or a nearby USB host.
+
Stays onlineThe board, stable power, 2.4 GHz Wi-Fi, and internet access.
+
+ +This is a third-party firmware path. Confirm the board and displayed build before flashing. + +## Version and source check + +| Item | Recorded reference | +|---|---| +| Flasher | [observer.gessaman.com](https://observer.gessaman.com/) | +| Recorded source branch | `mqtt-bridge-implementation-flex` | +| Recorded source commit | `c0c845f5` | +| Expected presets | `meshcore-ca-1` and `meshcore-ca-2` | + +The recorded commit is provenance, not proof that the live flasher still serves it. Stop if the flasher identifies a different board, source, or build that you have not reviewed. + +## Before you start + +!!! danger "Back up before enabling Erase device" + Erasing can remove the private identity key, name, radio settings, region data, and other saved configuration. Back up the private key and record the board, role, firmware version, and settings. If an identity cannot be backed up, stop and use a new board or ask for help. + +- [ ] Confirm the exact board is listed by the observer flasher. +- [ ] Decide whether this node should repeat traffic or observe only. +- [ ] Back up the existing identity and settings. +- [ ] Keep a known-good data USB cable and the board's recovery method nearby. +- [ ] Choose a real [location code](../iata-codes.md). +- [ ] Plan to test on the bench before deployment. + +## What this changes -Flash standalone MQTT observer firmware with the [MeshCore observer flasher](https://observer.gessaman.com/). MeshCore Canada no longer hosts separate observer firmware binaries on this page. +Flashing replaces the board firmware. Setup then changes the name, radio values, path-hash mode, location label, Wi-Fi credentials, broker presets, packet mode, and repeat behaviour. -!!! success "MeshCore Canada presets verified" - The observer firmware currently offered by `observer.gessaman.com` is built from Adam Gessaman's `mqtt-bridge-implementation-flex` branch at commit `c0c845f5`. That branch includes the built-in presets `meshcore-ca-1` and `meshcore-ca-2`, pointing to `mqtt1.meshcore.ca` and `mqtt2.meshcore.ca`. +The command builder runs locally in this browser. It does not store Wi-Fi fields, place them in the URL, or include them in the default preview. -## Flash The Observer +## Set up + +### 1. Flash the board 1. Open [observer.gessaman.com](https://observer.gessaman.com/). -2. Pick your board under **MQTT Observer Firmware**. +2. Under **MQTT Observer Firmware**, choose the exact supported board. 3. Choose **Repeater** or **Room Server**. -4. For a new board or a board you are repurposing, enable **Erase device** and flash the merged image. -5. When flashing finishes, use **Configure via USB** for the repeater or room server setup screen, or use **Console** for CLI setup. +4. For a new or intentionally repurposed board, enable **Erase device** only after the backup gate above. +5. Flash the merged image. +6. When flashing finishes, prefer **Configure via USB**. Use **Console** only for settings the setup screen does not expose. -!!! warning "First flash can erase settings" - First flashing observer firmware, especially on boards with a changed partition layout, can wipe stored settings and identity data. Back up an existing device private key before repurposing it. +**Expected:** the flasher reports completion and the board reconnects on USB. -## Required MeshCore Canada Settings +### 2. Enter the shared settings -Use the repeater or room server setup screen where possible. If a setting is not exposed in the setup screen, use the console on the flasher page and paste the CLI commands below. +Use the local mesh settings. When no community override exists, the Canadian onboarding baseline is: | Setting | Value | -|---------|-------| +|---|---| | Radio preset | **USA/Canada (Recommended)** | | Raw radio values | `910.525 MHz / 62.5 kHz / SF7 / CR5` | -| CLI radio command | `set radio 910.525,62.5,7,5` | -| Path hash mode | 3-byte advert path hashes: `set path.hash.mode 2` | -| MQTT slot 1 | `meshcore-ca-1` | -| MQTT slot 2 | `meshcore-ca-2` | -| IATA | A real 3-letter IATA airport code near the observer | -| WiFi | 2.4 GHz network credentials | - -!!! note "IATA codes" - Use a real airport code such as `YOW`, `YYZ`, `YUL`, `YVR`, or `YYC`. Do not use placeholders such as `XXX` or `HOME`. Do not use `CAN` as shorthand for Canada; it is an airport code for Guangzhou. See the [IATA region code list](../iata-codes.md) for Canadian quick choices. +| Path hashes | 3 bytes (`set path.hash.mode 2`) | +| Primary preset | `meshcore-ca-1` | +| Backup preset | `meshcore-ca-2` | +| Wi-Fi | A 2.4 GHz network | -## Command Builder +### 3. Build a reviewed CLI block -Use this builder to create a CLI block for the flasher **Console**. It runs only in your browser. +The CLI has no documented general quoting contract. The builder rejects spaces, quotes, backslashes, control characters, and other ambiguous Wi-Fi values. Use **Configure via USB** for a network it cannot represent safely. -
+
+ - -
+

Loading Canadian location suggestions…

+

SSID and password stay in this page only. They are cleared when you leave and hidden from the preview until you reveal them.

+ +
- + + +
-
+
Complete the required fields to build commands.
- - - - -## Manual CLI Reference - -If you prefer to type commands manually, replace `YOW`, `YourWiFiNetwork`, and `YourWiFiPassword` with your own values: +Review the non-sensitive summary and redacted preview. Reveal and copy only on a trusted computer. The clipboard then contains Wi-Fi credentials; clear it after use. + +### Manual CLI path + +If you prefer manual entry, set the non-sensitive values first: ```text set name YOW-Repeater-01 set radio 910.525,62.5,7,5 set path.hash.mode 2 set mqtt.iata YOW -set wifi.ssid YourWiFiNetwork -set wifi.pwd YourWiFiPassword set wifi.powersave none set mqtt1.preset meshcore-ca-1 set mqtt2.preset meshcore-ca-2 @@ -252,65 +175,64 @@ set mqtt.tx advert set bridge.enabled on set repeat on advert -reboot ``` -For an observe-only node that should not repeat mesh traffic, use: +Replace the name and location code. Use `set repeat off` for an observe-only node. + +Enter credentials directly in the device console: ```text -set repeat off +set wifi.ssid +set wifi.pwd +reboot ``` -## Verify +Do not paste completed credential lines into chat, issues, screenshots, logs, or saved notes. Do not add generic shell quotes to a device CLI value. + +## Expected result -After the device reboots, reopen the flasher **Console** and run: +After reboot, run: ```text +get name get wifi.status get mqtt.iata get mqtt1.preset get mqtt2.preset get mqtt.status +get mqtt.packets +get bridge.enabled get path.hash.mode ``` -Expected broker presets: +Expected: -```text -get mqtt1.preset -> meshcore-ca-1 -get mqtt2.preset -> meshcore-ca-2 -``` +- Wi-Fi and MQTT report connected; +- the location code is correct; +- presets are `meshcore-ca-1` and `meshcore-ca-2`; +- packet publishing and bridge mode are on; and +- path hash mode is `2`. -Once WiFi and MQTT are connected, use [Check Your Observer](../verify.md) to confirm packets are reaching MeshCore Canada. +## Verify in CoreScope -## Useful Links +1. Find the observer in [CoreScope Observers](https://live.meshcore.ca/#/observers). +2. Wait for normal nearby radio activity. +3. Confirm a recent packet in [CoreScope Packets](https://live.meshcore.ca/#/packets). -
+Complete [Check your observer](../verify.md). Connected Wi-Fi and MQTT are not end-to-end proof. -- :material-flash:{ .lg .middle } **Observer Flasher** +## Recovery - --- +If the board does not reboot or reconnect: - Flash MQTT observer firmware and open the serial console. +1. disconnect and reconnect with a known-good data cable; +2. use the board-specific recovery/boot sequence published by the flasher; +3. reflash the exact correct target without erasing again unless recovery requires it; +4. restore the backed-up identity and settings only to the intended board; and +5. repeat the local CLI checks before returning the board to service. - [:octicons-arrow-right-24: observer.gessaman.com](https://observer.gessaman.com/) +If the firmware runs but observing fails, restore the recorded prior settings or reflash the last known-good reviewed build. Keep the private identity backup private. -- :material-airplane:{ .lg .middle } **IATA Region Codes** +## If verification fails - --- - - Pick the real 3-letter airport code nearest to the observer. - - [:octicons-arrow-right-24: IATA codes](../iata-codes.md) - -- :material-check-circle:{ .lg .middle } **Check Your Observer** - - --- - - Confirm that the observer is online and reporting. - - [:octicons-arrow-right-24: verify status](../verify.md) - -
+Use [symptom-first troubleshooting](../troubleshooting.md). Share only read-command output after redacting private details; never share Wi-Fi commands or a private key. diff --git a/docs/analyzer/builds/pymc.md b/docs/analyzer/builds/pymc.md index 2ff309d..48f97d4 100644 --- a/docs/analyzer/builds/pymc.md +++ b/docs/analyzer/builds/pymc.md @@ -1,33 +1,76 @@ -# PyMC (Python MeshCore) +--- +title: Observe with PyMC +description: Add the Canadian endpoint pair to an existing PyMC repeater with a config backup, service check, rollback, and live proof. +audience: + - observer-operators + - service-operators +task: configure-pymc-observer +scope: canada-baseline +status: draft +owner: meshcore-canada +last_reviewed: 2026-07-19 +review_by: 2026-10-19 +difficulty: advanced +estimated_time: 25 minutes +destructive: true +page_styles: + - assets/styles/analyzer.css +--- + +# Observe with PyMC + +Add MeshCore Canada's primary and backup endpoints to a PyMC repeater service you already operate. + +## Is this method right for you? + +
+
Choose it whenA working PyMC repeater service already manages the radio.
+
Choose another method whenYou would install Python and PyMC only to observe traffic.
+
Stays onlineThe PyMC host, service, radio connection, and internet access.
+
+ +## Supported environment + +This guide assumes a Linux service named `pymc-repeater` and config at `/etc/pymc_repeater/config.yaml`. MeshCore Canada has not published a pinned PyMC/Python support matrix for this page. Confirm the installed version, service name, config path, and upstream format before editing. + +| Environment | Coverage in this guide | +|---|---| +| Linux with systemd and the default paths | Documented path; still confirm the installed PyMC release | +| Linux with a different service or config path | Adapt only after checking the local unit and upstream documentation | +| macOS or Windows | Not covered by this service procedure | +| PyMC and Python versions | Use the versions supported by the installed upstream PyMC release; no version pair is verified here | + +## Before you start + +- [ ] PyMC is healthy before the change. +- [ ] You know the actual service and config paths. +- [ ] The radio uses the local mesh settings. +- [ ] You chose a real [location code](../iata-codes.md). +- [ ] You can restore a root-owned backup. + +Record the current service state: -Add the MeshCore.ca broker pair to an existing pyMC repeater installation. PyMC connects to your MeshCore device and forwards traffic to MQTT brokers using a YAML config file. - -## Prerequisites +```bash +sudo systemctl status pymc-repeater --no-pager +sudo cp -- /etc/pymc_repeater/config.yaml /etc/pymc_repeater/config.yaml.pre-meshcore-ca +``` -| Requirement | Details | -|-------------|---------| -| PyMC | A working pyMC repeater installation | -| IATA Code | Your real 3-letter IATA airport code (e.g. `YOW` for Ottawa) | -| Mesh Settings | Repeater radio uses `USA/Canada (Recommended)` and 3-byte path hashes | +## What this changes -## Configuration +You edit PyMC's YAML config and restart its service. The change adds a location code and two encrypted, token-authenticated broker entries. It does not change radio firmware. -### 1. Set Your IATA Code +## Set up -In `/etc/pymc_repeater/config.yaml`, set your region code under the MQTT section: +In `/etc/pymc_repeater/config.yaml`, set the location code inside `mqtt`: ```yaml mqtt: iata_code: YOW ``` -Use the real 3-letter airport code nearest to you. The public broker rejects placeholders and made-up region names such as `XXX` or `HOME`. Do not use `CAN` as shorthand for Canada; it is a real airport code for Guangzhou and will tag your observer to the wrong region. +Replace `YOW` with the real code nearest the observer. -Also confirm the underlying repeater is on the MeshCore Canada network settings: **USA/Canada (Recommended)**, or raw radio values `910.525 MHz / 62.5 kHz / SF7 / CR5`, with 3-byte path hashes. - -### 2. Add the Broker Block - -Paste the following under `mqtt.brokers` in your config file: +Under `mqtt.brokers`, add: ```yaml - name: MeshCore-CA @@ -58,35 +101,47 @@ Paste the following under `mqtt.brokers` in your config file: audience: mqtt2.meshcore.ca ``` -### 3. Optional Fields +Do not add an MQTT password. Leave optional owner email blank unless it is operationally needed. -You can also set these optional fields in the `mqtt` section: +Review the edited section, then restart: -```yaml -mqtt: - owner: "your-public-key" - email: "you@example.com" +```bash +sudo systemctl restart pymc-repeater +sudo systemctl status pymc-repeater --no-pager ``` -### 4. Restart the Service +If the service fails, inspect a short local excerpt: ```bash -sudo systemctl restart pymc-repeater +sudo journalctl -u pymc-repeater -n 80 --no-pager ``` -## Quick Reference +Review and redact output before sharing it. + +## Expected result + +The service remains active without YAML, TLS, or token errors. Its packet counter or logs change when the connected radio hears nearby traffic. + +## Verify in CoreScope + +1. Find the observer in [CoreScope Observers](https://live.meshcore.ca/#/observers). +2. Wait for normal nearby activity. +3. Confirm a recent packet in [CoreScope Packets](https://live.meshcore.ca/#/packets). + +Complete [Check your observer](../verify.md). A healthy systemd service is not end-to-end proof. + +## Recovery + +Restore the exact backup made before editing: + +```bash +sudo cp -- /etc/pymc_repeater/config.yaml.pre-meshcore-ca /etc/pymc_repeater/config.yaml +sudo systemctl restart pymc-repeater +sudo systemctl status pymc-repeater --no-pager +``` -| Setting | Value | -|---------|-------| -| Config file | `/etc/pymc_repeater/config.yaml` | -| Primary broker | `mqtt1.meshcore.ca` | -| Backup broker | `mqtt2.meshcore.ca` | -| Port | `443` | -| Transport | `websockets` | -| TLS | Enabled, verified | -| Auth | JWT token (`use_jwt_auth: true`) | -| Format | `letsmesh` | +Keep the failed edited file privately if it is useful for diagnosis, but do not post it without redacting secrets and personal fields. -## Verify +## If verification fails -After restarting, head to [Check Your Observer](../verify.md) to confirm it's reporting correctly. +Use [symptom-first troubleshooting](../troubleshooting.md). Include the PyMC version, service name, first failed stage, and a short redacted log excerpt. diff --git a/docs/analyzer/data-collection-access.md b/docs/analyzer/data-collection-access.md index 518d2e8..e79b0b1 100644 --- a/docs/analyzer/data-collection-access.md +++ b/docs/analyzer/data-collection-access.md @@ -1,47 +1,81 @@ -# MQTT Data Collection & Access - -!!! warning "Treat MeshCore RF traffic as public data" - MeshCore traffic is intended for shared mesh use, and different networks may use different presets or frequencies (including non-default settings). All channels that use a shared public key (and private keys) should be considered inherently insecure. Any node transmitting MeshCore packets over matching settings can be heard by observers on that mesh, not just one published default profile. Traffic forwarded over MQTT through this path should be treated as potentially public. Do not transmit names, locations, notes, or other personal information unless you are comfortable with that information being stored and viewable publicly. Assume that even with encryption on a private channel / setting can potentially be collected and decrypted by anyone with the means and know-how to do so. - -## What We Collect - -MeshCore Canada MQTT receives packet data from observer nodes that capture MeshCore packets and forward telemetry from matched channels. - -Observers listen for all MeshCore traffic they can hear on the channels and presets they are configured for. If a packet is heard by an observer and that observer has packet publishing enabled, that traffic can be sent to the MeshCore Canada MQTT brokers. - -## Where Data Goes - -| Step | What happens | -|------|--------------| -| Radio traffic | Nodes transmit MeshCore packets on the frequencies and settings configured for their local mesh and presets. | -| Observer capture | MeshCore Canada observers and other authorized observers listen to all traffic they can hear on their configured channels. | -| MQTT publish | Observer paths publish packet data to MeshCore Canada MQTT infrastructure. | -| Storage and display | Data is stored on MeshCore Canada infrastructure and may be displayed by Beacon, CoreScope, and other public websites operated by MeshCore Canada or approved third-party operators. | - -## MQTT Subscription Access - -Direct MQTT subscription access is not handed out to everyone. It is limited to local mesh administrators, approved tools, and people approved by MeshCore Canada administration. - -Even when direct broker subscription access is limited, the data can still be viewable by everyone through Beacon, CoreScope, and other public websites that consume the MQTT feed using approved MQTT read accounts. - -## MQTT Read Access - -| Tool or service | Operator | Purpose | -|-----------------|----------|---------| -| Beacon | MeshCore Canada operators | Public viewer for MeshCore packet data. | -| CoreScope (`live.meshcore.ca`) | MeshCore Canada operators | Public observer, packet, and map tools. | - -## Infrastructure Administrators - -The MeshCore Canada infrastructure administrators control the MQTT brokers and related infrastructure. - -| Administrator | Profile | -|---------------|---------| -| Mr. Alderson | [github.com/MrAlders0n](https://github.com/MrAlders0n) | -| Ded | [github.com/446564](https://github.com/446564) | -| n30nex | [github.com/n30nex](https://github.com/n30nex) | -| Kranic | [forum.meshcore.ca/u/djkranic](https://forum.meshcore.ca/u/djkranic) | - -Questions about privacy, MQTT access, or the MeshCore Canada project should be directed to these administrators. - -General discussion and support is also available on the forum at [https://forum.meshcore.ca/](https://forum.meshcore.ca/). +--- +title: Observer data, access, and privacy +description: Understand what an observer can send, where that data goes, who can see it, and which retention details remain unpublished. +audience: + - observer-operators + - community-members +task: understand-observer-data +scope: canada-baseline +status: draft +owner: meshcore-canada +last_reviewed: 2026-07-19 +review_by: 2026-10-19 +difficulty: beginner +estimated_time: 6 minutes +destructive: false +page_styles: + - assets/styles/analyzer.css +--- + +# Observer data, access, and privacy + +Treat radio traffic heard by a public observer as public. Do not put private names, locations, credentials, or other sensitive information in MeshCore messages. + +
+
CollectionAn observer can send packets and status it hears on its configured radio settings.
+
DisplayApproved public tools can show observer, packet, and map data.
+
Direct accessBroker subscriptions are limited; public viewers may still expose the data.
+
+ +## Policy summary + +| Field | Current statement | +|---|---| +| Status | Draft operational summary | +| Version | `0.1` | +| Reviewed | July 19, 2026 | +| Owner | MeshCore Canada infrastructure administrators | +| Contact | [MeshCore Canada forum](https://forum.meshcore.ca/) | +| Retention | A public retention period has not yet been published | + +Because a retention period is not published, do not assume data will be deleted after a particular time. Ask the infrastructure team before relying on a deletion timeline. + +## Data flow + +
    +
  1. Radio packettransmitted on the configured mesh
  2. +
  3. Observerhears and forwards telemetry
  4. +
  5. Infrastructurereceives and may store it
  6. +
  7. Approved viewermay display it publicly
  8. +
+ +Changing the radio preset changes what the observer can hear. Public and private channel choices do not make the surrounding packet telemetry private. + +## Collection, access, and retention + +| Data | Why it is used | Where it may appear | Who can access it | Retention | +|---|---|---|---|---| +| Observer status | Show whether an observer is online | CoreScope and approved tools | Public viewers; infrastructure operators | Not publicly specified | +| Heard packet telemetry | Show mesh activity and diagnose coverage | Packet views, maps, approved tools | Public viewers; approved subscribers; infrastructure operators | Not publicly specified | +| Location code | Group an observer near a known place | Observer lists, topics, maps | Public viewers and broker users | Follows observer/packet retention | +| Optional owner details | Help an operator identify a service | Integration-dependent status data | May reach infrastructure and approved tools | Not publicly specified | +| Broker credentials | Authenticate the observer | Should remain only in the local integration | Local operator and authentication service | Never include in public diagnostics | + +MeshCore Canada does not offer general direct broker subscriptions. Access is limited to approved tools, local mesh administrators, and people approved by the infrastructure administrators. + +## Public tools + +- [CoreScope](https://live.meshcore.ca/) shows observer, packet, and map information. +- Other approved MeshCore Canada tools may consume the same feed. + +The existence of access controls on the broker does not make data private once an approved public viewer displays it. + +## Before you operate an observer + +- [ ] Tell people using the local mesh that public observation is active. +- [ ] Use a broad observer name, not a home address or person's name. +- [ ] Leave optional owner email fields blank unless they are operationally needed. +- [ ] Never paste Wi-Fi passwords, MQTT tokens, private keys, or unredacted logs into support messages. +- [ ] Read the method's verification and recovery steps before making changes. + +Return to [Choose an observer method](intro.md), or review [safe troubleshooting and redaction](troubleshooting.md#build-a-safe-escalation-bundle). diff --git a/docs/analyzer/iata-codes.md b/docs/analyzer/iata-codes.md index 7523741..b396012 100644 --- a/docs/analyzer/iata-codes.md +++ b/docs/analyzer/iata-codes.md @@ -1,147 +1,67 @@ -# IATA Region Codes - -Each observer identifies its region with a real 3-letter IATA airport code. Use the airport code nearest to the observer's real location. - -The firmware and helper scripts are not limited to the list below. If your nearest real IATA code is missing here, you can still use it and the public broker will accept it as long as it is a valid airport code. The live site will add observed regions to the picker automatically, but codes missing from the friendly-name list may appear as the bare code until we add a label. - -Do not use placeholders or made-up region names such as `XXX` or `HOME`. Do not use `CAN` as shorthand for Canada; it is a real airport code for Guangzhou and will tag your observer to the wrong region. - -Host-side helper scripts show this same quick list interactively when you omit `--iata`. - -??? note "Ontario" - - | Code | Region | - |------|--------| - | YYZ | Toronto (Pearson) | - | YTZ | Toronto (Billy Bishop) | - | YOW | Ottawa | - | YHM | Hamilton | - | YKF | Kitchener / Waterloo | - | YXU | London | - | YOO | Oshawa | - | YKZ | Buttonville / Markham | - | YAM | Sault Ste. Marie | - | YQT | Thunder Bay | - | YSB | Sudbury | - | YTS | Timmins | - | YQG | Windsor | - | YYB | North Bay | - | YGK | Kingston | - | YPQ | Peterborough | - | YTR | Trenton / Quinte West | - | YHD | Dryden | - | YPL | Pickle Lake | - -??? note "Quebec" - - | Code | Region | - |------|--------| - | YUL | Montreal (Trudeau) | - | YMX | Montreal (Mirabel) | - | YQB | Quebec City | - | YND | Gatineau (Ottawa area) | - | YBG | Bagotville / Saguenay | - | YVO | Val-d'Or | - | YHU | Montreal (St-Hubert) | - | YRJ | Roberval | - | YGL | La Grande Riviere | - | YSC | Sherbrooke | - | YTQ | Tasiujaq | - | YUY | Rouyn-Noranda | - | YZV | Sept-Iles | - | YGP | Gaspe | - | YRQ | Trois-Rivieres | - | YBC | Baie-Comeau | - -??? note "British Columbia" - - | Code | Region | - |------|--------| - | YVR | Vancouver | - | YYJ | Victoria | - | YXX | Abbotsford / Fraser Valley | - | YLW | Kelowna | - | YXS | Prince George | - | YPR | Prince Rupert | - | YXT | Terrace | - | YQQ | Comox / Courtenay | - | YCD | Nanaimo | - | YYD | Smithers | - | YDQ | Dawson Creek | - | YXJ | Fort St. John | - | YYF | Penticton | - | YCG | Castlegar | - | YKA | Kamloops | - | YXC | Cranbrook | - -??? note "Alberta" - - | Code | Region | - |------|--------| - | YYC | Calgary | - | YEG | Edmonton | - | YMM | Fort McMurray | - | YQU | Grande Prairie | - | YQL | Lethbridge | - | YXH | Medicine Hat | - -??? note "Saskatchewan" - - | Code | Region | - |------|--------| - | YQR | Regina | - | YXE | Saskatoon | - | YPA | Prince Albert | - -??? note "Manitoba" - - | Code | Region | - |------|--------| - | YWG | Winnipeg | - | YBR | Brandon | - | YTH | Thompson | - | YDN | Dauphin | - | YPG | Portage la Prairie | - -??? note "New Brunswick" - - | Code | Region | - |------|--------| - | YFC | Fredericton | - | YSJ | Saint John | - | YQM | Moncton | - | ZBF | Bathurst | - -??? note "Nova Scotia" - - | Code | Region | - |------|--------| - | YHZ | Halifax | - | YQY | Sydney | - | YQI | Yarmouth | - -??? note "Prince Edward Island" - - | Code | Region | - |------|--------| - | YYG | Charlottetown | - -??? note "Newfoundland and Labrador" - - | Code | Region | - |------|--------| - | YYT | St. John's | - | YQX | Gander | - | YDF | Deer Lake | - | YYR | Goose Bay | - | YWK | Wabush | - -??? note "Territories (YT / NT / NU)" - - | Code | Region | - |------|--------| - | YXY | Whitehorse (Yukon) | - | YZF | Yellowknife (NWT) | - | YFB | Iqaluit (Nunavut) | - | YEV | Inuvik (NWT) | - | YHY | Hay River (NWT) | +--- +title: Find an observer location code +description: Search the Canadian quick list for the real three-letter airport code nearest to an observer. +audience: + - observer-operators +task: choose-location-code +scope: canada-baseline +status: draft +owner: meshcore-canada +last_reviewed: 2026-07-19 +review_by: 2026-10-19 +difficulty: beginner +estimated_time: 3 minutes +destructive: false +page_styles: + - assets/styles/analyzer.css +page_scripts: + - assets/javascripts/analyzer-location-codes.js +--- + +# Find an observer location code + +An observer uses a real three-letter airport code as a broad location label. It does not define a MeshCore region boundary. + +Use the nearest sensible code for the observer's actual area. Use the same code in every broker entry. + +
+
+ + +
+

Loading location codes…

+
+ + + + + + + + + +
CodePlaceProvince or territory
+
+
+ +## About this list + +The [canonical location-code data](location-codes.json) generates the search tool and the command-builder suggestions. It is a curated Canadian quick list, not a complete official airport-code registry. + +If the nearest real airport code is missing: + +1. verify it against a reliable airport source; +2. type the three-letter code into a method that accepts free text; and +3. ask MeshCore Canada to add the friendly place name. + +Do not use `CAN` for Canada; it is an airport code for Guangzhou. Do not use placeholders such as `XXX` or `HOME`. + +Return to [Choose an observer method](intro.md). diff --git a/docs/analyzer/intro.md b/docs/analyzer/intro.md index 23e8044..5693cfb 100644 --- a/docs/analyzer/intro.md +++ b/docs/analyzer/intro.md @@ -1,124 +1,91 @@ -# Analyzer & MQTT Packet Broker - -MeshCore observers capture mesh traffic and publish packet telemetry to MQTT brokers, feeding CoreScope dashboards, maps, and packet inspectors. Pick the observer path that matches your hardware and host setup. - -!!! tip "Observer setup checklist" - Every observer path needs the same basics: a MeshCore radio already on the MeshCore Canada network settings, a real 3-letter IATA airport code, the MeshCore.ca broker pair, JWT token authentication, TLS on port `443`, and packet publishing enabled. If the setup screen has two broker entries, use the same IATA code on both entries. - - MeshCore Canada network settings are **USA/Canada (Recommended)**, or raw radio values `910.525 MHz / 62.5 kHz / SF7 / CR5`, with 3-byte path hashes. Standalone observer firmware from the observer flasher includes selectable MeshCore Canada broker presets; set `meshcore-ca-1` and `meshcore-ca-2`, then set `set path.hash.mode 2`, IATA, WiFi, and packet publishing during onboarding. On retained preferences or generic CLI devices, also run `set radio 910.525,62.5,7,5`. - -## Choose Your Observer Path - -
- -- :material-chip:{ .lg .middle } **MQTT Firmware** - - --- - - Flash observer firmware directly onto a WiFi-capable LoRa board. No host computer required after setup. - - Best for: Heltec V3, Heltec V4 OLED, and other published direct MQTT targets. - - [:octicons-arrow-right-24: MQTT Firmware Guide](builds/mqtt-firmware.md) - -- :material-usb:{ .lg .middle } **MCtoMQTT** - - --- - - Bridge a USB-connected MeshCore node to MQTT via a Linux or macOS host. - - Best for: fixed repeaters and room servers with a nearby host machine. - - [:octicons-arrow-right-24: MCtoMQTT Guide](builds/mctomqtt.md) - -- :material-language-python:{ .lg .middle } **PyMC** - - --- - - Add the MeshCore.ca broker pair to an existing pyMC repeater installation. - - Best for: Python-based repeater setups. - - [:octicons-arrow-right-24: PyMC Guide](builds/pymc.md) - -- :material-home-assistant:{ .lg .middle } **Home Assistant** - - --- - - Add MeshCore.ca brokers to the Home Assistant MeshCore integration. - - Best for: Home Assistant users with a connected MeshCore node. - - [:octicons-arrow-right-24: MeshCore-HA Guide](builds/meshcore-ha.md) - -- :octicons-terminal-24:{ .lg .middle } **RemoteTerm** - - --- - - Use RemoteTerm's Community MQTT fanout to report packets from a managed radio. - - Best for: RemoteTerm users already connected over serial, TCP, or BLE. - - [:octicons-arrow-right-24: RemoteTerm Setup](remoteterm.md) - +--- +title: Set up a network observer +description: Choose a simple, supported way to send nearby MeshCore radio activity to the Canadian live network tools. +audience: + - observer-operators +task: choose-observer-method +scope: canada-baseline +status: draft +owner: meshcore-canada +last_reviewed: 2026-07-19 +review_by: 2026-10-19 +difficulty: beginner +estimated_time: 5 minutes +destructive: false +page_styles: + - assets/styles/analyzer.css +page_scripts: + - assets/javascripts/analyzer-method-chooser.js +--- + +# Set up a network observer + +An observer listens for nearby MeshCore radio packets and sends a copy to MeshCore Canada's live tools. It does not read private messages, and it does not need to repeat traffic. + +## How the pieces fit + +
    +
  1. Radiohears nearby MeshCore packets
  2. +
  3. Observerpasses those packets to the internet
  4. +
  5. MeshCore Canadareceives them through two shared endpoints
  6. +
  7. Live toolsshow observer and packet health
  8. +
+ +The shared endpoints use MQTT, a delivery protocol. You do not need to learn MQTT before choosing a method. + +!!! warning "Radio traffic may become public" + Observer telemetry and heard radio packets can appear in public tools. Do not transmit sensitive information. Read [Data, access, and privacy](data-collection-access.md) before operating an observer. + +## Pick what you already use + +Choose the closest match. The result links to one focused guide. + +
+ +
-## Shared References - -
- -- :material-check-circle:{ .lg .middle } **Check Your Observer** - - --- - - Confirm your observer is online and reporting to CoreScope. - - [:octicons-arrow-right-24: Check Your Observer](verify.md) +No JavaScript? Use the same comparison below. -- :material-wrench:{ .lg .middle } **Troubleshooting** +| Your current setup | Recommended method | Host that must stay on | +|---|---|---| +| RemoteTerm already manages the radio | [RemoteTerm](remoteterm.md) | The RemoteTerm host | +| Home Assistant already has MeshCore | [Home Assistant](builds/meshcore-ha.md) | Home Assistant | +| PyMC already manages a repeater | [PyMC](builds/pymc.md) | The PyMC host | +| A Linux or macOS host is connected by USB | [MCtoMQTT](builds/mctomqtt.md) | The USB host | +| A supported Wi-Fi LoRa board can be dedicated | [Standalone MQTT firmware](builds/mqtt-firmware.md) | No separate host | - --- +If none fit, ask in the [MeshCore Canada forum](https://forum.meshcore.ca/) before installing more software. - Path-specific diagnostics for firmware, host bridges, PyMC, Home Assistant, and RemoteTerm. +## Shared setup values - [:octicons-arrow-right-24: Troubleshooting](troubleshooting.md) +Every method uses: -- :material-server-network:{ .lg .middle } **Broker Reference** +- a radio already set for the local mesh; +- a real three-letter [location code](iata-codes.md); +- the MeshCore Canada primary and backup endpoints; +- encrypted connections with certificate checks; and +- packet publishing, not status-only publishing. - --- +The Canadian onboarding baseline is **USA/Canada (Recommended)**, `910.525 MHz / 62.5 kHz / SF7 / CR5`, with 3-byte path hashes. A published local setting takes priority. - Broker hosts, ports, TLS, JWT audience, and topic conventions. - - [:octicons-arrow-right-24: Broker Details](broker-reference.md) - -- :material-eye:{ .lg .middle } **Data Collection & Access** - - --- - - Learn what observer data is collected, where it is stored, and who administers MQTT access. - - [:octicons-arrow-right-24: Data Collection & Access](data-collection-access.md) - -- :material-airplane:{ .lg .middle } **IATA Codes** - - --- - - Canadian quick-list codes and guidance for choosing a real region code. - - [:octicons-arrow-right-24: IATA Region Codes](iata-codes.md) - -
+Advanced operators can inspect the [canonical observer configuration](observer-config.json) or [broker reference](broker-reference.md). -## Fast Path +## What success looks like -If you are unsure which path to choose: +Setup is not finished when a screen says “connected.” Finish when both are true: -| Situation | Start here | -|-----------|------------| -| You have a WiFi-capable LoRa board and want a standalone observer | [MQTT Firmware](builds/mqtt-firmware.md) | -| You have a repeater connected to a Linux/macOS host over USB | [MCtoMQTT](builds/mctomqtt.md) | -| You already run pyMC | [PyMC](builds/pymc.md) | -| You already use Home Assistant for MeshCore | [MeshCore-HA](builds/meshcore-ha.md) | -| You already manage the radio with RemoteTerm | [RemoteTerm](remoteterm.md) | +1. your observer appears in [CoreScope Observers](https://live.meshcore.ca/#/observers); and +2. a packet heard by your radio appears in [CoreScope Packets](https://live.meshcore.ca/#/packets). -After setup, use [Check Your Observer](verify.md). If it does not appear within a few minutes, use [Troubleshooting](troubleshooting.md). +Follow [Check your observer](verify.md) at the end of the method you choose. If a check fails, use [symptom-first troubleshooting](troubleshooting.md). diff --git a/docs/analyzer/location-codes.json b/docs/analyzer/location-codes.json new file mode 100644 index 0000000..b81d3b5 --- /dev/null +++ b/docs/analyzer/location-codes.json @@ -0,0 +1,101 @@ +{ + "schema_version": 1, + "version": "2026.07", + "last_reviewed": "2026-07-19", + "scope": "Curated Canadian quick list; not a complete official airport-code registry.", + "locations": [ + {"code": "YYZ", "name": "Toronto Pearson", "province": "Ontario", "province_code": "ON"}, + {"code": "YTZ", "name": "Toronto Billy Bishop", "province": "Ontario", "province_code": "ON"}, + {"code": "YOW", "name": "Ottawa", "province": "Ontario", "province_code": "ON"}, + {"code": "YHM", "name": "Hamilton", "province": "Ontario", "province_code": "ON"}, + {"code": "YKF", "name": "Kitchener / Waterloo", "province": "Ontario", "province_code": "ON"}, + {"code": "YXU", "name": "London", "province": "Ontario", "province_code": "ON"}, + {"code": "YOO", "name": "Oshawa", "province": "Ontario", "province_code": "ON"}, + {"code": "YKZ", "name": "Buttonville / Markham", "province": "Ontario", "province_code": "ON"}, + {"code": "YAM", "name": "Sault Ste. Marie", "province": "Ontario", "province_code": "ON"}, + {"code": "YQT", "name": "Thunder Bay", "province": "Ontario", "province_code": "ON"}, + {"code": "YSB", "name": "Sudbury", "province": "Ontario", "province_code": "ON"}, + {"code": "YTS", "name": "Timmins", "province": "Ontario", "province_code": "ON"}, + {"code": "YQG", "name": "Windsor", "province": "Ontario", "province_code": "ON"}, + {"code": "YYB", "name": "North Bay", "province": "Ontario", "province_code": "ON"}, + {"code": "YGK", "name": "Kingston", "province": "Ontario", "province_code": "ON"}, + {"code": "YPQ", "name": "Peterborough", "province": "Ontario", "province_code": "ON"}, + {"code": "YTR", "name": "Trenton / Quinte West", "province": "Ontario", "province_code": "ON"}, + {"code": "YHD", "name": "Dryden", "province": "Ontario", "province_code": "ON"}, + {"code": "YPL", "name": "Pickle Lake", "province": "Ontario", "province_code": "ON"}, + + {"code": "YUL", "name": "Montreal Trudeau", "province": "Quebec", "province_code": "QC"}, + {"code": "YMX", "name": "Montreal Mirabel", "province": "Quebec", "province_code": "QC"}, + {"code": "YQB", "name": "Quebec City", "province": "Quebec", "province_code": "QC"}, + {"code": "YND", "name": "Gatineau / Ottawa area", "province": "Quebec", "province_code": "QC"}, + {"code": "YBG", "name": "Bagotville / Saguenay", "province": "Quebec", "province_code": "QC"}, + {"code": "YVO", "name": "Val-d'Or", "province": "Quebec", "province_code": "QC"}, + {"code": "YHU", "name": "Montreal St-Hubert", "province": "Quebec", "province_code": "QC"}, + {"code": "YRJ", "name": "Roberval", "province": "Quebec", "province_code": "QC"}, + {"code": "YGL", "name": "La Grande Riviere", "province": "Quebec", "province_code": "QC"}, + {"code": "YSC", "name": "Sherbrooke", "province": "Quebec", "province_code": "QC"}, + {"code": "YTQ", "name": "Tasiujaq", "province": "Quebec", "province_code": "QC"}, + {"code": "YUY", "name": "Rouyn-Noranda", "province": "Quebec", "province_code": "QC"}, + {"code": "YZV", "name": "Sept-Iles", "province": "Quebec", "province_code": "QC"}, + {"code": "YGP", "name": "Gaspe", "province": "Quebec", "province_code": "QC"}, + {"code": "YRQ", "name": "Trois-Rivieres", "province": "Quebec", "province_code": "QC"}, + {"code": "YBC", "name": "Baie-Comeau", "province": "Quebec", "province_code": "QC"}, + + {"code": "YVR", "name": "Vancouver", "province": "British Columbia", "province_code": "BC"}, + {"code": "YYJ", "name": "Victoria", "province": "British Columbia", "province_code": "BC"}, + {"code": "YXX", "name": "Abbotsford / Fraser Valley", "province": "British Columbia", "province_code": "BC"}, + {"code": "YLW", "name": "Kelowna", "province": "British Columbia", "province_code": "BC"}, + {"code": "YXS", "name": "Prince George", "province": "British Columbia", "province_code": "BC"}, + {"code": "YPR", "name": "Prince Rupert", "province": "British Columbia", "province_code": "BC"}, + {"code": "YXT", "name": "Terrace", "province": "British Columbia", "province_code": "BC"}, + {"code": "YQQ", "name": "Comox / Courtenay", "province": "British Columbia", "province_code": "BC"}, + {"code": "YCD", "name": "Nanaimo", "province": "British Columbia", "province_code": "BC"}, + {"code": "YYD", "name": "Smithers", "province": "British Columbia", "province_code": "BC"}, + {"code": "YDQ", "name": "Dawson Creek", "province": "British Columbia", "province_code": "BC"}, + {"code": "YXJ", "name": "Fort St. John", "province": "British Columbia", "province_code": "BC"}, + {"code": "YYF", "name": "Penticton", "province": "British Columbia", "province_code": "BC"}, + {"code": "YCG", "name": "Castlegar", "province": "British Columbia", "province_code": "BC"}, + {"code": "YKA", "name": "Kamloops", "province": "British Columbia", "province_code": "BC"}, + {"code": "YXC", "name": "Cranbrook", "province": "British Columbia", "province_code": "BC"}, + + {"code": "YYC", "name": "Calgary", "province": "Alberta", "province_code": "AB"}, + {"code": "YEG", "name": "Edmonton", "province": "Alberta", "province_code": "AB"}, + {"code": "YMM", "name": "Fort McMurray", "province": "Alberta", "province_code": "AB"}, + {"code": "YQU", "name": "Grande Prairie", "province": "Alberta", "province_code": "AB"}, + {"code": "YQL", "name": "Lethbridge", "province": "Alberta", "province_code": "AB"}, + {"code": "YXH", "name": "Medicine Hat", "province": "Alberta", "province_code": "AB"}, + + {"code": "YQR", "name": "Regina", "province": "Saskatchewan", "province_code": "SK"}, + {"code": "YXE", "name": "Saskatoon", "province": "Saskatchewan", "province_code": "SK"}, + {"code": "YPA", "name": "Prince Albert", "province": "Saskatchewan", "province_code": "SK"}, + + {"code": "YWG", "name": "Winnipeg", "province": "Manitoba", "province_code": "MB"}, + {"code": "YBR", "name": "Brandon", "province": "Manitoba", "province_code": "MB"}, + {"code": "YTH", "name": "Thompson", "province": "Manitoba", "province_code": "MB"}, + {"code": "YDN", "name": "Dauphin", "province": "Manitoba", "province_code": "MB"}, + {"code": "YPG", "name": "Portage la Prairie", "province": "Manitoba", "province_code": "MB"}, + + {"code": "YFC", "name": "Fredericton", "province": "New Brunswick", "province_code": "NB"}, + {"code": "YSJ", "name": "Saint John", "province": "New Brunswick", "province_code": "NB"}, + {"code": "YQM", "name": "Moncton", "province": "New Brunswick", "province_code": "NB"}, + {"code": "ZBF", "name": "Bathurst", "province": "New Brunswick", "province_code": "NB"}, + + {"code": "YHZ", "name": "Halifax", "province": "Nova Scotia", "province_code": "NS"}, + {"code": "YQY", "name": "Sydney", "province": "Nova Scotia", "province_code": "NS"}, + {"code": "YQI", "name": "Yarmouth", "province": "Nova Scotia", "province_code": "NS"}, + + {"code": "YYG", "name": "Charlottetown", "province": "Prince Edward Island", "province_code": "PE"}, + + {"code": "YYT", "name": "St. John's", "province": "Newfoundland and Labrador", "province_code": "NL"}, + {"code": "YQX", "name": "Gander", "province": "Newfoundland and Labrador", "province_code": "NL"}, + {"code": "YDF", "name": "Deer Lake", "province": "Newfoundland and Labrador", "province_code": "NL"}, + {"code": "YYR", "name": "Goose Bay", "province": "Newfoundland and Labrador", "province_code": "NL"}, + {"code": "YWK", "name": "Wabush", "province": "Newfoundland and Labrador", "province_code": "NL"}, + + {"code": "YXY", "name": "Whitehorse", "province": "Yukon", "province_code": "YT"}, + {"code": "YZF", "name": "Yellowknife", "province": "Northwest Territories", "province_code": "NT"}, + {"code": "YFB", "name": "Iqaluit", "province": "Nunavut", "province_code": "NU"}, + {"code": "YEV", "name": "Inuvik", "province": "Northwest Territories", "province_code": "NT"}, + {"code": "YHY", "name": "Hay River", "province": "Northwest Territories", "province_code": "NT"} + ] +} diff --git a/docs/analyzer/observer-config.json b/docs/analyzer/observer-config.json new file mode 100644 index 0000000..bb79abf --- /dev/null +++ b/docs/analyzer/observer-config.json @@ -0,0 +1,48 @@ +{ + "schema_version": 1, + "version": "2026.07", + "last_reviewed": "2026-07-19", + "network": { + "preset": "USA/Canada (Recommended)", + "frequency_mhz": 910.525, + "bandwidth_khz": 62.5, + "spreading_factor": 7, + "coding_rate": 5, + "path_hash_mode": 2, + "path_hash_bytes": 3 + }, + "brokers": [ + { + "id": "primary", + "name": "MeshCore Canada primary", + "host": "mqtt1.meshcore.ca", + "port": 443, + "transport": "websockets", + "tls": true, + "verify_tls": true, + "authentication": "MeshCore JWT token", + "token_audience": "mqtt1.meshcore.ca" + }, + { + "id": "backup", + "name": "MeshCore Canada backup", + "host": "mqtt2.meshcore.ca", + "port": 443, + "transport": "websockets", + "tls": true, + "verify_tls": true, + "authentication": "MeshCore JWT token", + "token_audience": "mqtt2.meshcore.ca" + } + ], + "topics": { + "packets": "meshcore/{IATA}/{PUBLIC_KEY}/packets", + "status": "meshcore/{IATA}/{PUBLIC_KEY}/status" + }, + "verification": { + "observers": "https://live.meshcore.ca/#/observers", + "packets": "https://live.meshcore.ca/#/packets", + "map": "https://live.meshcore.ca/#/map" + }, + "location_codes": "location-codes.json" +} diff --git a/docs/analyzer/remoteterm.md b/docs/analyzer/remoteterm.md index d5905d0..a78b0ca 100644 --- a/docs/analyzer/remoteterm.md +++ b/docs/analyzer/remoteterm.md @@ -1,47 +1,98 @@ -# RemoteTerm Setup +--- +title: Observe with RemoteTerm +description: Forward packets from a radio already managed by RemoteTerm, then verify the complete path in CoreScope. +audience: + - observer-operators +task: configure-remoteterm-observer +scope: canada-baseline +status: draft +owner: meshcore-canada +last_reviewed: 2026-07-19 +review_by: 2026-10-19 +difficulty: intermediate +estimated_time: 15 minutes +destructive: false +page_styles: + - assets/styles/analyzer.css +--- -[RemoteTerm for MeshCore](https://github.com/jkingsman/Remote-Terminal-for-MeshCore) can act as an observer by forwarding raw RF packets through its **Community MQTT / meshcoretomqtt** integration. This does not publish decrypted messages. +# Observe with RemoteTerm -Install and start RemoteTerm using the upstream instructions, connect your MeshCore radio over serial, TCP, or BLE, then open the RemoteTerm web UI. +[RemoteTerm for MeshCore](https://github.com/jkingsman/Remote-Terminal-for-MeshCore) can forward raw packets from a radio it already manages. It does not publish decrypted messages. -## Configure Community MQTT +## Is this method right for you? -In RemoteTerm, open **Settings** -> **MQTT & Automation**, add **Community MQTT/meshcoretomqtt**, and use these values: +
+
Choose it whenRemoteTerm already connects to the radio over serial, TCP, or BLE.
+
Choose another method whenYou would install RemoteTerm only for observing; compare the simpler host or standalone paths first.
+
Stays onlineThe RemoteTerm host, its radio connection, and internet access.
+
-| Field | Value | -|-------|-------| -| Name | `MeshCore.ca 1` or any descriptive name | +This guide follows RemoteTerm's rolling interface and is not pinned to a tested release. If labels differ, record the RemoteTerm version and check the upstream instructions before saving. + +## Before you start + +- [ ] RemoteTerm is installed from its reviewed upstream source. +- [ ] The radio connection is stable. +- [ ] The radio is on the local mesh settings. +- [ ] You chose a real [location code](iata-codes.md). +- [ ] You read [Observer data, access, and privacy](data-collection-access.md). + +## What this changes + +RemoteTerm gains one primary and one backup Community MQTT entry. Each entry sends packet telemetry through an encrypted WebSocket connection. It does not change radio firmware. + +## Set up + +Open **Settings** → **MQTT & Automation**, add **Community MQTT / meshcoretomqtt**, and enter: + +| Field | Primary value | +|---|---| +| Name | `MeshCore.ca 1` | | Broker Host | `mqtt1.meshcore.ca` | | Broker Port | `443` | | Transport | `WebSockets` | | Authentication | `Token` | | WebSocket Path | `/` | -| Token Audience | `mqtt1.meshcore.ca` or leave blank to default to the broker host | -| Owner Email | Optional | +| Token Audience | `mqtt1.meshcore.ca` | | Use TLS | Enabled | | Verify TLS certificates | Enabled | -| Region Code (IATA) | Your nearest real 3-letter IATA airport code | +| Region Code | Your nearest real three-letter location code | | Packet Topic Template | `meshcore/{IATA}/{PUBLIC_KEY}/packets` | -Click **Save as Enabled**. - -## Add the Backup Broker +Leave optional owner email blank unless it is operationally needed. Save the entry as enabled. -To publish to the redundant broker as well, add a second Community MQTT integration with the same settings, changing only: +Add a backup entry with the same values, changing only: -| Field | Value | -|-------|-------| +| Field | Backup value | +|---|---| | Name | `MeshCore.ca 2` | | Broker Host | `mqtt2.meshcore.ca` | | Token Audience | `mqtt2.meshcore.ca` | -Use the same IATA code on both entries. +Use the same location code in both entries. -![RemoteTerm Community MQTT settings for MeshCore.ca](../assets/mcterm.png) +![RemoteTerm Community MQTT settings for MeshCore Canada](../assets/mcterm.png) !!! note "Windows MQTT fanout" - If you run RemoteTerm on Windows and enable MQTT fanout, start Uvicorn with `--loop none` as described in the RemoteTerm README so the MQTT client can connect reliably. + If RemoteTerm's current upstream instructions require Uvicorn `--loop none` for Windows MQTT fanout, use that documented launch option. Confirm against the installed RemoteTerm version. + +## Expected result + +Both entries remain enabled without repeated TLS or token errors. Normal nearby radio activity increments packet handling in RemoteTerm. + +## Verify in CoreScope + +1. Open [CoreScope Observers](https://live.meshcore.ca/#/observers) and find the RemoteTerm observer. +2. Create or wait for normal nearby activity. +3. Open [CoreScope Packets](https://live.meshcore.ca/#/packets) and confirm a recent packet appears. + +Continue through the complete [observer verification checklist](verify.md). A connected entry without a recent packet is not complete. + +## Recovery + +Disable or remove only the two Community MQTT entries you added. Do not remove unrelated RemoteTerm automation. Confirm RemoteTerm still manages the radio normally. -## Verify +## If verification fails -After saving, use [Check Your Observer](verify.md). If the observer connects but no packets appear, confirm the radio is on the MeshCore Canada preset and that RemoteTerm is publishing packet topics, not only status. +Use [symptom-first troubleshooting](troubleshooting.md). For a missing backup only, compare its host and token audience; both must be `mqtt2.meshcore.ca`. diff --git a/docs/analyzer/scripts/add-meshcore-ca-broker.sh b/docs/analyzer/scripts/add-meshcore-ca-broker.sh index 87a41e9..619ab14 100644 --- a/docs/analyzer/scripts/add-meshcore-ca-broker.sh +++ b/docs/analyzer/scripts/add-meshcore-ca-broker.sh @@ -107,6 +107,8 @@ upper_iata() { printf '%s' "$1" | tr '[:lower:]' '[:upper:]' } +# Keep this quick-list snapshot aligned with ../location-codes.json. The +# analyzer content test fails when the published JSON and helper drift. IATA_CHOICES="$(cat <<'EOF' Ontario|YYZ|Toronto (Pearson) Ontario|YTZ|Toronto (Billy Bishop) @@ -142,6 +144,7 @@ Quebec|YUY|Rouyn-Noranda Quebec|YZV|Sept-Iles Quebec|YGP|Gaspe Quebec|YRQ|Trois-Rivieres +Quebec|YBC|Baie-Comeau British Columbia|YVR|Vancouver British Columbia|YYJ|Victoria British Columbia|YXX|Abbotsford / Fraser Valley @@ -158,7 +161,6 @@ British Columbia|YYF|Penticton British Columbia|YCG|Castlegar British Columbia|YKA|Kamloops British Columbia|YXC|Cranbrook -British Columbia|YBC|Baie-Comeau Alberta|YYC|Calgary Alberta|YEG|Edmonton Alberta|YMM|Fort McMurray @@ -273,8 +275,12 @@ EOF exit 1 fi IATA="$(upper_iata "$IATA" | tr -d '[:space:]')" - if [ "$IATA" = "XXX" ]; then - echo "XXX is a placeholder. Use the real 3-letter IATA airport code nearest to you." >&2 + if [ "$IATA" = "XXX" ] || [ "$IATA" = "HOME" ]; then + echo "$IATA is a placeholder. Use the real 3-letter IATA airport code nearest to you." >&2 + exit 1 + fi + if [ "$IATA" = "CAN" ]; then + echo "CAN is Guangzhou's airport code, not shorthand for Canada. Use the real code nearest to you." >&2 exit 1 fi if ! printf '%s' "$IATA" | grep -Eq '^[A-Z]{3}$'; then diff --git a/docs/analyzer/scripts/add-meshcore-ca-packetcapture-broker.ps1 b/docs/analyzer/scripts/add-meshcore-ca-packetcapture-broker.ps1 index 20fb8cf..9d747ea 100644 --- a/docs/analyzer/scripts/add-meshcore-ca-packetcapture-broker.ps1 +++ b/docs/analyzer/scripts/add-meshcore-ca-packetcapture-broker.ps1 @@ -31,6 +31,8 @@ function Prompt-YesNo { } } +# Keep this quick-list snapshot aligned with ../location-codes.json. The +# analyzer content test fails when the published JSON and helper drift. $KnownIataCodes = @( "YYZ","YTZ","YOW","YHM","YKF","YXU","YOO","YKZ","YAM","YQT","YSB","YTS","YQG","YYB","YGK","YPQ","YTR","YHD","YPL","YND", "YUL","YMX","YQB","YBG","YVO","YHU","YRJ","YGL","YSC","YTQ","YUY","YZV","YGP","YRQ", @@ -53,8 +55,11 @@ function Resolve-Iata { $candidate = Read-Host "Enter 3-letter IATA airport code" $candidate = $candidate.Trim().ToUpperInvariant() } - if ($candidate -eq "XXX") { - throw "XXX is a placeholder. Use the real 3-letter IATA airport code nearest to you." + if ($candidate -in @("XXX", "HOME")) { + throw "$candidate is a placeholder. Use the real 3-letter IATA airport code nearest to you." + } + if ($candidate -eq "CAN") { + throw "CAN is Guangzhou's airport code, not shorthand for Canada. Use the real code nearest to you." } if ($candidate -notmatch '^[A-Z]{3}$') { throw "IATA code must be exactly 3 letters." diff --git a/docs/analyzer/troubleshooting.md b/docs/analyzer/troubleshooting.md index 75496ef..f14e3e2 100644 --- a/docs/analyzer/troubleshooting.md +++ b/docs/analyzer/troubleshooting.md @@ -1,119 +1,157 @@ -# Troubleshooting - -If your observer doesn't show up on [CoreScope](https://live.meshcore.ca/#/observers), work through these checks based on your setup path. - -## MQTT Firmware - -Run these in the device's admin CLI: - -| Command | What to check | -|---------|---------------| -| `get wifi.status` | Should show connected to your 2.4 GHz network | -| `get mqtt.status` | Should show an active broker connection | -| `get mqtt.iata` | Should return your real 3-letter IATA airport code | -| `get mqtt.packets` | Should be `on` for packet publishing | -| `get bridge.enabled` | Should be `on` for bridge publishing | -| `get mqtt.rx` / `get mqtt.tx` | Should match the firmware guide setup | -| `get mqtt1.preset` | Should show `meshcore-ca-1` | -| `get mqtt2.preset` | Should show `meshcore-ca-2` | -| `get name` | Should return the node name you set | - -??? note "Full verify command block" - - ```text - get name - get mqtt.origin - get mqtt.iata - get wifi.status - get mqtt.packets - get bridge.enabled - get mqtt.rx - get mqtt.tx - get mqtt.status - get mqtt1.preset - get mqtt2.preset - get mqtt3.preset +--- +title: Troubleshoot an observer +description: Start from what you see, isolate the failed stage, and prepare a useful support report without exposing secrets. +audience: + - observer-operators +task: troubleshoot-observer +scope: canada-baseline +status: draft +owner: meshcore-canada +last_reviewed: 2026-07-19 +review_by: 2026-10-19 +difficulty: intermediate +estimated_time: 15 minutes +destructive: false +page_styles: + - assets/styles/analyzer.css +--- + +# Troubleshoot an observer + +Start with the symptom. Do not change the radio, bridge, broker, and viewer at the same time. + +## Observer never appears + +Work from the radio toward the live site: + +1. **Radio:** confirm it is powered, connected to the observer method, and on the local mesh settings. +2. **Device or service:** confirm the observer process is running. +3. **Broker:** confirm the primary endpoint reports connected with TLS verification. +4. **Viewer:** wait a few minutes, refresh [CoreScope Observers](https://live.meshcore.ca/#/observers), and search the exact observer name. + +Copy-safe service checks: + +=== "MCtoMQTT" + + ```bash + sudo systemctl status mctomqtt --no-pager + sudo journalctl -u mctomqtt -n 80 --no-pager ``` -## MCtoMQTT / Companion (USB Host) +=== "Companion capture" -Check that the systemd service is running: + ```bash + sudo systemctl status meshcore-capture --no-pager + sudo journalctl -u meshcore-capture -n 80 --no-pager + ``` -```bash -# Serial host -sudo systemctl status mctomqtt +=== "PyMC" -# Companion -sudo systemctl status meshcore-capture -``` + ```bash + sudo systemctl status pymc-repeater --no-pager + sudo journalctl -u pymc-repeater -n 80 --no-pager + ``` -If the service is running but nothing appears, check the config drop-in: +Review output before sharing it. Redact tokens, passwords, private keys, Wi-Fi details, owner email, exact home location, and full configuration files. -```bash -# Serial host -cat /etc/mctomqtt/config.d/20-meshcore-ca.toml +For standalone firmware, run only these read commands in the device CLI: -# Companion -cat ~/.meshcore-packet-capture/.env.local +```text +get name +get wifi.status +get mqtt.iata +get mqtt.status +get mqtt1.preset +get mqtt2.preset +get path.hash.mode ``` -Confirm the broker hosts are `mqtt1.meshcore.ca` and `mqtt2.meshcore.ca`. +**Expected:** Wi-Fi and at least the primary broker report connected; the location code is three letters; the primary and backup presets are `meshcore-ca-1` and `meshcore-ca-2`. -Also confirm the observer path is publishing packet payloads, not only status. For companion capture, `PACKETCAPTURE_MQTT1_TOPIC_PACKETS` and `PACKETCAPTURE_MQTT2_TOPIC_PACKETS` should use `meshcore/{IATA}/{PUBLIC_KEY}/packets`. +## Observer appears but no packets arrive -## IATA Code Problems +A connected broker is not packet proof. -Use a real 3-letter IATA airport code such as `YOW`, `YKF`, or `YYZ`. The firmware may accept any text, but the public broker rejects placeholders and made-up region names such as `XXX` or `HOME`. Do not use `CAN` as shorthand for Canada; it is a real airport code for Guangzhou and will tag your observer to the wrong region. +1. Confirm the radio can hear normal nearby mesh activity. +2. Confirm packet publishing is enabled for the selected method. +3. Confirm the location code and packet topic agree. +4. Check [CoreScope Packets](https://live.meshcore.ca/#/packets) after nearby activity. -If your code is not on the quick list, that does not automatically mean it is unsupported. It can still work if it is a real IATA airport code. Make sure every component uses the same code: +| Method | Packet setting | +|---|---| +| MQTT firmware | `get mqtt.packets` is `on`, `get bridge.enabled` is `on`, and `get mqtt.rx` is `on` | +| MCtoMQTT / companion capture | Packet topic ends in `/packets`, not only `/status` | +| PyMC | Broker `format` is `letsmesh` | +| Home Assistant | **Payload Mode** is `packet`, or older **Packets (Lets Mesh)** is enabled | +| RemoteTerm | Community MQTT packet topic is enabled | -| Component | Where to check | -|-----------|----------------| -| MQTT firmware | `get mqtt.iata` | -| MCtoMQTT | `/etc/mctomqtt/config.d/20-meshcore-ca.toml` | -| Companion capture | `PACKETCAPTURE_IATA` in `~/.meshcore-packet-capture/.env.local` | -| Home Assistant | MeshCore integration region/IATA field | -| RemoteTerm | Community MQTT region/IATA field | +If the radio hears nothing, resolve the radio preset, antenna, connection, or local activity before changing broker values. -## PyMC +## Only the backup connection fails -```bash -sudo systemctl status pymc-repeater -``` +Compare the two entries. The backup host and token audience must both be `mqtt2.meshcore.ca`. A token for the primary host cannot authenticate to the backup. + +Do not disable TLS verification to make the connection succeed. + +## Observer appears in the wrong place + +Check every configured location field. Use the same real three-letter [location code](iata-codes.md) in both broker entries and in the observer method. -Check that your `mqtt.iata_code` is set and the broker block is present in `/etc/pymc_repeater/config.yaml`. +Do not use: -## Home Assistant +- `CAN` as shorthand for Canada; +- `XXX` or `HOME`; or +- a neighbouring code merely because an older picker does not list the correct code. -Go to **Settings** > **Devices & Services** > **MeshCore** > **Configure** > **Manage MQTT Brokers** and confirm both brokers show as connected. Make sure your IATA code is set in the integration. +Update the integration if it cannot accept the correct code. -If the brokers connect but packets never appear, check the packet payload setting. Some Home Assistant MeshCore versions label this as **Packets (Lets Mesh)**; newer versions expose it as **Payload Mode**. It must be enabled/set to `packet` for MeshCore.ca packet visibility. +## Observer connects and disconnects repeatedly -If a code such as `YTR` is missing from a picker, update the MeshCore Home Assistant integration and type the code into **Broker IATA Code**. Using a nearby code such as `YGK` can make data visible, but it tags the observer to the wrong region. +Check in this order: -| HA symptom | Check | -|------------|-------| -| Broker connected, no packets | **Packets (Lets Mesh)** enabled or **Payload Mode** = `packet` | -| Cannot enter a real IATA code | Update MeshCore-HA; current versions use free text | -| Backup broker fails | `Token Audience` must match the broker host (`mqtt2.meshcore.ca`) | -| Observer appears under the wrong city | Both broker entries use the same nearest real IATA code | +1. stable power and USB connection; +2. host sleep or service restarts; +3. internet and DNS stability; +4. system clock accuracy; +5. repeated token, TLS, or WebSocket errors in local logs. -## RemoteTerm +Record the time and time zone of one disconnect. That lets an administrator compare your report with infrastructure logs without exposing credentials. -Open **Settings** -> **MQTT & Automation** and confirm each Community MQTT entry: +## Home Assistant screen does not match the guide + +MeshCore integration labels have changed over time. Current screens may show **Payload Mode** and a free-text location field; older screens may show **Packets (Lets Mesh)** and a picker. + +Update the integration before substituting a wrong location. If the current screen still differs, record the Home Assistant and MeshCore integration versions and ask for help. + +## Build a safe escalation bundle + +Share a short, redacted report: + +```text +Observer method: +Device or board: +Operating system / Home Assistant version: +Observer app, integration, or firmware version: +Location code: +Time checked (with time zone): +First failed stage: radio / observer / broker / viewer +Primary connected: yes / no / unknown +Backup connected: yes / no / not supported +Observer visible: yes / no +Recent packet visible: yes / no +Exact error after redaction: +Steps already tried: +``` -| Field | Expected value | -|-------|----------------| -| Broker Host | `mqtt1.meshcore.ca` or `mqtt2.meshcore.ca` | -| Broker Port | `443` | -| Transport | `WebSockets` | -| Authentication | `Token` | -| TLS | Enabled and verified | -| Region Code | Your nearest real IATA code | -| Packet Topic Template | `meshcore/{IATA}/{PUBLIC_KEY}/packets` | +Before posting, remove: -If the primary entry works and the backup does not, check the second entry's token audience. It must be `mqtt2.meshcore.ca`. +- Wi-Fi SSID and password; +- MQTT passwords, JWTs, tokens, cookies, and authorization headers; +- MeshCore private keys; +- owner email and personal contact details; +- exact home addresses or coordinates; and +- unrelated lines from full config files. -## Still Not Working? +Post the bundle in the [MeshCore Canada forum](https://forum.meshcore.ca/) or the support channel used by your local community. Include a small relevant log excerpt, not an unreviewed archive. -If everything looks correct but your observer still doesn't appear, double check that your device has internet access and can reach `mqtt1.meshcore.ca` on port 443. Firewalls or network restrictions on outbound WebSocket connections are the most common blocker. +Return to [Check your observer](verify.md) after each fix. diff --git a/docs/analyzer/verify.md b/docs/analyzer/verify.md index 7681c9c..908313e 100644 --- a/docs/analyzer/verify.md +++ b/docs/analyzer/verify.md @@ -1,64 +1,88 @@ -# Check Your Observer - -After setting up your observer using any supported path ([MQTT Firmware](builds/mqtt-firmware.md), [MCtoMQTT](builds/mctomqtt.md), [PyMC](builds/pymc.md), [Home Assistant](builds/meshcore-ha.md), or [RemoteTerm](remoteterm.md)), use the links below to confirm it's online and reporting. - -
- -- :material-eye:{ .lg .middle } **CoreScope Observers** - - --- - - See all connected observers and their current status. - - [:octicons-arrow-right-24: View Observers](https://live.meshcore.ca/#/observers) - -- :material-swap-horizontal:{ .lg .middle } **CoreScope Packets** - - --- - - Watch live packet traffic flowing through observers in real time. - - [:octicons-arrow-right-24: View Packets](https://live.meshcore.ca/#/packets) - -- :material-map-marker:{ .lg .middle } **MeshCore Map** - - --- - - See observers and nodes plotted on the map. - - [:octicons-arrow-right-24: View Map](https://live.meshcore.ca/#/map) - -- :material-wrench:{ .lg .middle } **Troubleshooting** - - --- - - Not showing up? Path-specific diagnostics for all observer types. - - [:octicons-arrow-right-24: Troubleshooting](troubleshooting.md) - -
- -Your observer should appear within a few minutes of coming online. - -## Healthy Observer Checklist +--- +title: Check your observer +description: Prove that the radio hears traffic, the observer sends it, and MeshCore Canada's live tools receive it. +audience: + - observer-operators +task: verify-observer +scope: canada-baseline +status: draft +owner: meshcore-canada +last_reviewed: 2026-07-19 +review_by: 2026-10-19 +difficulty: beginner +estimated_time: 10 minutes +destructive: false +page_styles: + - assets/styles/analyzer.css +--- + +# Check your observer + +A broker connection proves only that the internet link works. An observer is healthy when a real radio packet reaches the live packet view. + +## Verify all four stages + +
    +
  1. + Radio: confirm the device is on the local mesh settings and can hear nearby activity. +
    Expected: a packet counter, log, or connected application changes when a nearby node transmits. +
  2. +
  3. + Observer: check the method's service, integration, or device status. +
    Expected: it reports an encrypted connection to at least the primary endpoint without repeating authentication errors. +
  4. +
  5. + Observer view: open CoreScope Observers. +
    Expected: the observer name and three-letter location code appear with a recent timestamp. +
  6. +
  7. + Packet view: create nearby MeshCore activity, then open CoreScope Packets. +
    Expected: a recent packet attributed to the observer appears within a few minutes. +
  8. +
+ +Do not generate unnecessary traffic on a busy mesh. A normal advert or existing nearby activity is enough. + +## Healthy result | Check | Expected result | -|-------|-----------------| -| Observer name | Clear node name such as `YOW-Repeater-01` | -| Region | Nearest real IATA code, not `CAN`, `XXX`, or `HOME` | -| Broker coverage | Primary and backup broker configured where the path supports both | -| Packet activity | Recent packet timestamps on CoreScope after nearby mesh activity | -| Radio settings | `USA/Canada (Recommended)` and 3-byte path hashes unless your local page differs | - -## First Checks - -If it does not appear, start with the checks that match the symptom: - -| Symptom | First check | -|---------|-------------| -| No observer entry | MQTT is connected and the IATA code is a real airport code | -| Observer appears, but no packets | Packet publishing is enabled: firmware `mqtt.packets`, HA **Payload Mode** = `packet`, or pyMC `format: letsmesh` | -| Backup broker does not connect | The token audience matches the broker host (`mqtt2.meshcore.ca` for broker 2) | -| Observer appears under the wrong city | Every broker/integration entry uses the nearest real IATA code; do not use `CAN` for Canada | - -For path-specific commands and Home Assistant settings, use [Troubleshooting](troubleshooting.md). +|---|---| +| Name | A clear service name such as `YOW-Repeater-01`, without a home address | +| Location | The nearest real three-letter airport code, not `CAN`, `XXX`, or `HOME` | +| Primary path | Connected to `mqtt1.meshcore.ca` with TLS certificate verification | +| Backup path | Connected to `mqtt2.meshcore.ca` where the method supports two entries | +| Packet mode | Packet publishing is enabled, not status-only | +| Packet time | A recent packet appears after the radio hears nearby traffic | +| Radio | Local settings are used; the Canada baseline applies only when no local override exists | + +## Record the check + +Keep a small private maintenance note: + +```text +Observer: +Method and version: +Location code: +Checked at (include time zone): +Radio heard a packet: yes / no +Primary connected: yes / no +Backup connected: yes / no / not supported +Observer visible: yes / no +Packet visible: yes / no +``` + +Do not include credentials, private keys, Wi-Fi names, or Wi-Fi passwords. + +## If a stage fails + +Start with the first failed stage: + +| First failure | Next page | +|---|---| +| Radio does not hear activity | [Observer appears but no packets](troubleshooting.md#observer-appears-but-no-packets-arrive) | +| Observer cannot connect | [Observer never appears](troubleshooting.md#observer-never-appears) | +| Observer is visible but packet view stays empty | [Observer appears but no packets](troubleshooting.md#observer-appears-but-no-packets-arrive) | +| Backup alone fails | [Only the backup connection fails](troubleshooting.md#only-the-backup-connection-fails) | +| Place or name is wrong | [Observer appears in the wrong place](troubleshooting.md#observer-appears-in-the-wrong-place) | + +Use [Troubleshooting](troubleshooting.md) for copy-safe checks and a redacted support bundle. diff --git a/docs/assets/javascripts/analyzer-broker-reference.js b/docs/assets/javascripts/analyzer-broker-reference.js new file mode 100644 index 0000000..a6091a2 --- /dev/null +++ b/docs/assets/javascripts/analyzer-broker-reference.js @@ -0,0 +1,63 @@ +(function () { + "use strict"; + + function cell(row, value) { + var item = document.createElement("td"); + item.textContent = String(value); + row.appendChild(item); + } + + function validConfig(config) { + return config && + config.schema_version === 1 && + Array.isArray(config.brokers) && + config.brokers.length === 2 && + config.brokers.every(function (broker) { + return /^mqtt[12]\.meshcore\.ca$/.test(broker.host) && + broker.port === 443 && + broker.transport === "websockets" && + broker.tls === true && + broker.verify_tls === true && + broker.token_audience === broker.host; + }); + } + + async function init() { + var root = document.getElementById("broker-reference"); + if (!root || root.dataset.ready === "true") return; + var body = document.getElementById("broker-reference-body"); + var status = document.getElementById("broker-reference-status"); + if (!body || !status || !root.dataset.source) return; + + root.dataset.ready = "true"; + try { + var response = await fetch(root.dataset.source, {credentials: "same-origin"}); + if (!response.ok) throw new Error("configuration request failed"); + var config = await response.json(); + if (!validConfig(config)) throw new Error("configuration is invalid"); + + var fragment = document.createDocumentFragment(); + config.brokers.forEach(function (broker) { + var row = document.createElement("tr"); + cell(row, broker.id === "primary" ? "Primary" : "Backup"); + cell(row, broker.host); + cell(row, broker.port); + cell(row, "WebSockets"); + cell(row, "Required; verify certificates"); + cell(row, broker.token_audience); + fragment.appendChild(row); + }); + body.replaceChildren(fragment); + status.textContent = "Observer configuration " + config.version + + ", reviewed " + config.last_reviewed + "."; + } catch (_error) { + status.textContent = "The generated broker table could not be loaded. Open the canonical JSON below."; + } + } + + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", init, {once: true}); + } else { + init(); + } +})(); diff --git a/docs/assets/javascripts/analyzer-command-builder.js b/docs/assets/javascripts/analyzer-command-builder.js new file mode 100644 index 0000000..38e9da7 --- /dev/null +++ b/docs/assets/javascripts/analyzer-command-builder.js @@ -0,0 +1,251 @@ +(function () { + "use strict"; + + function utf8Length(text) { + if (window.TextEncoder) return new TextEncoder().encode(text).length; + return unescape(encodeURIComponent(text)).length; + } + + function safeCliToken(text) { + // MeshCore's device CLI does not publish a general quoting contract. + // Reject ambiguous values instead of guessing shell-style escaping. + return /^[A-Za-z0-9!#$%&()*+,./:=?@^_~-]+$/.test(text); + } + + function markInvalid(field, invalid) { + field.setAttribute("aria-invalid", invalid ? "true" : "false"); + } + + function redactedCommands(commands) { + return commands.map(function (line) { + if (line.indexOf("set wifi.ssid ") === 0) return "set wifi.ssid [hidden]"; + if (line.indexOf("set wifi.pwd ") === 0) return "set wifi.pwd [hidden]"; + return line; + }); + } + + async function populateLocations(root, datalist, status) { + var source = root.dataset.locationSource; + if (!source || !datalist) return; + try { + var response = await fetch(source, {credentials: "same-origin"}); + if (!response.ok) throw new Error("location data request failed"); + var payload = await response.json(); + if (!payload || payload.schema_version !== 1 || !Array.isArray(payload.locations)) { + throw new Error("location data is invalid"); + } + var fragment = document.createDocumentFragment(); + payload.locations.forEach(function (location) { + if (!/^[A-Z]{3}$/.test(location.code)) return; + var option = document.createElement("option"); + option.value = location.code; + option.label = location.name + ", " + location.province; + fragment.appendChild(option); + }); + datalist.replaceChildren(fragment); + status.textContent = payload.locations.length + " Canadian quick-list codes loaded."; + } catch (_error) { + status.textContent = "Location suggestions are unavailable. You can still enter a real 3-letter airport code."; + } + } + + function init() { + var root = document.getElementById("observer-command-builder"); + if (!root || root.dataset.ready === "true") return; + + var fields = { + board: document.getElementById("observer-board"), + iata: document.getElementById("observer-iata"), + role: document.getElementById("observer-role"), + number: document.getElementById("observer-number"), + ssid: document.getElementById("observer-ssid"), + password: document.getElementById("observer-password"), + repeat: document.getElementById("observer-repeat") + }; + var output = document.getElementById("observer-command-output"); + var status = document.getElementById("observer-copy-status"); + var errors = document.getElementById("observer-command-errors"); + var summary = document.getElementById("observer-command-summary"); + var copyButton = document.getElementById("observer-copy-commands"); + var revealCommandsButton = document.getElementById("observer-reveal-commands"); + var togglePasswordButton = document.getElementById("observer-toggle-password"); + var clearButton = document.getElementById("observer-clear-secrets"); + var datalist = document.getElementById("observer-iata-list"); + var locationStatus = document.getElementById("observer-location-status"); + if ( + Object.values(fields).some(function (field) { return !field; }) || + !output || !status || !errors || !summary || !copyButton || + !revealCommandsButton || !togglePasswordButton || !clearButton + ) { + return; + } + + root.dataset.ready = "true"; + var exactCommands = []; + var commandsRevealed = false; + + function hideExactCommands() { + commandsRevealed = false; + revealCommandsButton.textContent = "Reveal sensitive commands"; + revealCommandsButton.setAttribute("aria-pressed", "false"); + copyButton.disabled = true; + } + + function showCurrentCommands() { + output.textContent = (commandsRevealed ? exactCommands : redactedCommands(exactCommands)).join("\n"); + revealCommandsButton.textContent = commandsRevealed + ? "Hide sensitive commands" + : "Reveal sensitive commands"; + revealCommandsButton.setAttribute("aria-pressed", commandsRevealed ? "true" : "false"); + copyButton.disabled = !commandsRevealed || !exactCommands.length; + } + + function renderSummary(board, iata, nodeName, repeat) { + var values = [ + ["Board", fields.board.options[fields.board.selectedIndex].text], + ["Location", iata || "Not set"], + ["Node name", nodeName || "Not set"], + ["Mesh traffic", repeat === "on" ? "Observe and repeat" : "Observe only"] + ]; + var fragment = document.createDocumentFragment(); + values.forEach(function (entry) { + var wrapper = document.createElement("div"); + var term = document.createElement("dt"); + var detail = document.createElement("dd"); + term.textContent = entry[0]; + detail.textContent = entry[1]; + wrapper.appendChild(term); + wrapper.appendChild(detail); + fragment.appendChild(wrapper); + }); + summary.replaceChildren(fragment); + summary.dataset.board = board; + } + + function render() { + var messages = []; + var board = fields.board.value; + var iata = (fields.iata.value || "").trim().toUpperCase(); + var role = fields.role.value; + var number = (fields.number.value || "").trim(); + var ssid = fields.ssid.value || ""; + var password = fields.password.value || ""; + var repeat = fields.repeat.value; + var nodeName = iata && number ? iata + "-" + role + "-" + number : ""; + + [fields.iata, fields.number, fields.ssid, fields.password].forEach(function (field) { + markInvalid(field, false); + }); + + if (!/^[A-Z]{3}$/.test(iata) || iata === "XXX" || iata === "CAN") { + messages.push("Enter a real 3-letter airport code; do not use XXX or CAN."); + markInvalid(fields.iata, true); + } + if (!/^[A-Za-z0-9][A-Za-z0-9_-]{0,15}$/.test(number)) { + messages.push("Node number must use 1–16 letters, numbers, underscores, or hyphens."); + markInvalid(fields.number, true); + } + if (nodeName && utf8Length(nodeName) > 24) { + messages.push("The generated node name is over 24 bytes; shorten the node number."); + markInvalid(fields.number, true); + } + if (!ssid || utf8Length(ssid) > 32 || !safeCliToken(ssid)) { + messages.push("Enter a 1–32 byte SSID without spaces, quotes, backslashes, semicolons, pipes, control characters, or non-ASCII text. Use Configure via USB for other SSIDs."); + markInvalid(fields.ssid, true); + } + if (!password || utf8Length(password) > 64 || !safeCliToken(password)) { + messages.push("Enter a 1–64 byte password using the safe CLI character set, or use Configure via USB for this network."); + markInvalid(fields.password, true); + } + + hideExactCommands(); + errors.textContent = messages.join(" "); + renderSummary(board, iata, nodeName, repeat); + + if (messages.length) { + exactCommands = []; + revealCommandsButton.disabled = true; + output.textContent = "Complete the required fields to build commands."; + return; + } + + exactCommands = [ + "set name " + nodeName, + "set radio 910.525,62.5,7,5", + "set path.hash.mode 2", + "set mqtt.iata " + iata, + "set wifi.ssid " + ssid, + "set wifi.pwd " + password, + "set wifi.powersave none", + "set mqtt1.preset meshcore-ca-1", + "set mqtt2.preset meshcore-ca-2", + "set mqtt3.preset none", + "set mqtt4.preset none", + "set mqtt5.preset none", + "set mqtt6.preset none", + "set mqtt.status on", + "set mqtt.packets on", + "set mqtt.raw off", + "set mqtt.rx on", + "set mqtt.tx advert", + "set bridge.enabled on", + "set repeat " + repeat, + "advert", + "reboot" + ]; + revealCommandsButton.disabled = false; + showCurrentCommands(); + } + + function clearSecrets() { + fields.ssid.value = ""; + fields.password.value = ""; + fields.password.type = "password"; + togglePasswordButton.textContent = "Show"; + togglePasswordButton.setAttribute("aria-pressed", "false"); + exactCommands = []; + hideExactCommands(); + output.textContent = "Wi-Fi fields cleared."; + errors.textContent = ""; + fields.ssid.focus(); + } + + root.addEventListener("input", render); + root.addEventListener("change", render); + togglePasswordButton.addEventListener("click", function () { + var showing = fields.password.type === "text"; + fields.password.type = showing ? "password" : "text"; + togglePasswordButton.textContent = showing ? "Show" : "Hide"; + togglePasswordButton.setAttribute("aria-pressed", showing ? "false" : "true"); + fields.password.focus(); + }); + revealCommandsButton.addEventListener("click", function () { + if (!exactCommands.length) return; + commandsRevealed = !commandsRevealed; + showCurrentCommands(); + }); + copyButton.addEventListener("click", function () { + if (!commandsRevealed || !exactCommands.length) return; + if (!navigator.clipboard || !navigator.clipboard.writeText) { + status.textContent = "Clipboard unavailable. Copy the revealed commands manually."; + return; + } + navigator.clipboard.writeText(exactCommands.join("\n")).then(function () { + status.textContent = "Copied. Your clipboard now contains Wi-Fi credentials."; + }, function () { + status.textContent = "Copy failed. Copy the revealed commands manually."; + }); + }); + clearButton.addEventListener("click", clearSecrets); + window.addEventListener("pagehide", clearSecrets); + + populateLocations(root, datalist, locationStatus); + render(); + } + + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", init, {once: true}); + } else { + init(); + } +})(); diff --git a/docs/assets/javascripts/analyzer-location-codes.js b/docs/assets/javascripts/analyzer-location-codes.js new file mode 100644 index 0000000..96eacbf --- /dev/null +++ b/docs/assets/javascripts/analyzer-location-codes.js @@ -0,0 +1,104 @@ +(function () { + "use strict"; + + function textCell(text) { + var cell = document.createElement("td"); + cell.textContent = text; + return cell; + } + + function validPayload(payload) { + if (!payload || payload.schema_version !== 1 || !Array.isArray(payload.locations)) return false; + var seen = new Set(); + return payload.locations.every(function (location) { + if ( + !location || + !/^[A-Z]{3}$/.test(location.code) || + !/^[A-Z]{2}$/.test(location.province_code) || + typeof location.name !== "string" || + typeof location.province !== "string" || + seen.has(location.code) + ) { + return false; + } + seen.add(location.code); + return true; + }); + } + + async function init() { + var root = document.getElementById("location-code-tool"); + if (!root || root.dataset.ready === "true") return; + + var search = document.getElementById("location-code-search"); + var province = document.getElementById("location-code-province"); + var body = document.getElementById("location-code-results"); + var status = document.getElementById("location-code-status"); + var source = root.dataset.source; + if (!search || !province || !body || !status || !source) return; + + root.dataset.ready = "true"; + status.textContent = "Loading location codes…"; + + try { + var response = await fetch(source, {credentials: "same-origin"}); + if (!response.ok) throw new Error("location data request failed"); + var payload = await response.json(); + if (!validPayload(payload)) throw new Error("location data is invalid"); + + var locations = payload.locations.slice().sort(function (left, right) { + return left.province.localeCompare(right.province) || left.name.localeCompare(right.name); + }); + var provinces = Array.from(new Set(locations.map(function (item) { + return item.province; + }))).sort(); + + provinces.forEach(function (name) { + var option = document.createElement("option"); + option.value = name; + option.textContent = name; + province.appendChild(option); + }); + + function render() { + var query = search.value.trim().toLocaleLowerCase("en-CA"); + var selectedProvince = province.value; + var matches = locations.filter(function (location) { + var searchable = [ + location.code, + location.name, + location.province, + location.province_code + ].join(" ").toLocaleLowerCase("en-CA"); + return (!query || searchable.includes(query)) && + (!selectedProvince || location.province === selectedProvince); + }); + + var fragment = document.createDocumentFragment(); + matches.forEach(function (location) { + var row = document.createElement("tr"); + row.appendChild(textCell(location.code)); + row.appendChild(textCell(location.name)); + row.appendChild(textCell(location.province)); + fragment.appendChild(row); + }); + body.replaceChildren(fragment); + status.textContent = matches.length === 1 + ? "1 location code shown." + : matches.length + " location codes shown."; + } + + search.addEventListener("input", render); + province.addEventListener("change", render); + render(); + } catch (_error) { + status.textContent = "The location list could not be loaded. Use the raw data link below or try again."; + } + } + + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", init, {once: true}); + } else { + init(); + } +})(); diff --git a/docs/assets/javascripts/analyzer-method-chooser.js b/docs/assets/javascripts/analyzer-method-chooser.js new file mode 100644 index 0000000..48cd015 --- /dev/null +++ b/docs/assets/javascripts/analyzer-method-chooser.js @@ -0,0 +1,68 @@ +(function () { + "use strict"; + + var methods = { + "remote-term": { + title: "Use RemoteTerm", + description: "Keep using the radio connection and management screen you already have.", + href: "../remoteterm/" + }, + "home-assistant": { + title: "Use Home Assistant", + description: "Add the MeshCore Canada broker pair inside your existing MeshCore integration.", + href: "../builds/meshcore-ha/" + }, + "pymc": { + title: "Use PyMC", + description: "Add the broker pair to the PyMC service you already operate.", + href: "../builds/pymc/" + }, + "usb-host": { + title: "Use MCtoMQTT", + description: "Run a small bridge on the Linux or macOS computer connected to the radio by USB.", + href: "../builds/mctomqtt/" + }, + "wifi-board": { + title: "Use standalone MQTT firmware", + description: "A supported Wi-Fi LoRa board can listen and publish without a separate computer.", + href: "../builds/mqtt-firmware/" + } + }; + + function init() { + var root = document.getElementById("observer-method-chooser"); + if (!root || root.dataset.ready === "true") return; + + var select = root.querySelector("select"); + var result = document.getElementById("observer-method-result"); + if (!select || !result) return; + + root.dataset.ready = "true"; + select.addEventListener("change", function () { + var method = methods[select.value]; + if (!method) { + result.hidden = true; + result.replaceChildren(); + return; + } + + var heading = document.createElement("strong"); + heading.textContent = method.title; + var detail = document.createElement("span"); + detail.textContent = method.description + " "; + var link = document.createElement("a"); + link.href = method.href; + link.textContent = "Open this setup guide"; + detail.appendChild(link); + result.replaceChildren(heading, detail); + result.hidden = false; + result.focus(); + }); + } + + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", init, {once: true}); + } else { + init(); + } +})(); diff --git a/docs/assets/javascripts/bootstrap.js b/docs/assets/javascripts/bootstrap.js new file mode 100644 index 0000000..d97cb35 --- /dev/null +++ b/docs/assets/javascripts/bootstrap.js @@ -0,0 +1,108 @@ +(function () { + "use strict"; + + var storagePrefix = "meshcore-canada:progress:v1:"; + + function storageKey(input) { + var pageKey = input.closest("[data-mc-progress-page]"); + var scope = pageKey ? pageKey.getAttribute("data-mc-progress-page") : window.location.pathname; + return storagePrefix + scope + ":" + input.id; + } + + function readProgress(input) { + try { + return window.localStorage.getItem(storageKey(input)) === "done"; + } catch (_error) { + return false; + } + } + + function writeProgress(input) { + try { + if (input.checked) { + window.localStorage.setItem(storageKey(input), "done"); + } else { + window.localStorage.removeItem(storageKey(input)); + } + } catch (_error) { + // The journey still works when storage is blocked or unavailable. + } + } + + function initialiseProgress() { + document.querySelectorAll("input[type='checkbox'][data-mc-progress]").forEach(function (input) { + if (!input.id || input.dataset.mcProgressReady === "true") return; + input.dataset.mcProgressReady = "true"; + input.checked = readProgress(input); + input.addEventListener("change", function () { + writeProgress(input); + }); + }); + } + + function labelExternalLinks() { + document.querySelectorAll("a[target='_blank']").forEach(function (link) { + if (link.dataset.mcExternalReady === "true") return; + link.dataset.mcExternalReady = "true"; + var current = link.getAttribute("aria-label"); + if (!current) { + var text = (link.textContent || "External link").trim(); + link.setAttribute("aria-label", text + " (opens in a new tab)"); + } + }); + } + + function labelThemeControls() { + var search = document.querySelector(".md-search[role='dialog']"); + if (search && !search.hasAttribute("aria-label")) { + search.setAttribute("aria-label", "Site search"); + } + + document.querySelectorAll("a[href]").forEach(function (link) { + if ((link.textContent || "").trim() !== "Start") return; + if (!/\/start\/(?:$|[?#])/.test(link.href)) return; + link.setAttribute("aria-label", "Start using MeshCore"); + }); + } + + function initialiseSearchInputs() { + document.querySelectorAll(".md-search__input").forEach(function (input) { + if (input.dataset.mcSearchReady === "true") return; + input.dataset.mcSearchReady = "true"; + + var pendingKeyup; + + input.addEventListener("input", function () { + window.clearTimeout(pendingKeyup); + pendingKeyup = window.setTimeout(function () { + input.dispatchEvent(new KeyboardEvent("keyup", { + bubbles: true, + key: "Unidentified", + code: "Unidentified" + })); + }, 60); + }); + + input.addEventListener("keyup", function (event) { + if (event.isTrusted) { + window.clearTimeout(pendingKeyup); + } + }); + }); + } + + function initialise() { + initialiseProgress(); + labelExternalLinks(); + labelThemeControls(); + initialiseSearchInputs(); + } + + if (window.document$ && typeof window.document$.subscribe === "function") { + window.document$.subscribe(initialise); + } else if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", initialise, { once: true }); + } else { + initialise(); + } +})(); diff --git a/docs/assets/javascripts/communities.js b/docs/assets/javascripts/communities.js new file mode 100644 index 0000000..f321ba3 --- /dev/null +++ b/docs/assets/javascripts/communities.js @@ -0,0 +1,113 @@ +(function () { + "use strict"; + + function normalize(value) { + return String(value || "") + .normalize("NFKD") + .replace(/[\u0300-\u036f]/g, "") + .toLocaleLowerCase() + .replace(/\s+/g, " ") + .trim(); + } + + function initializeDirectory(root) { + if (!root || root.dataset.communityReady === "true") { + return; + } + + var search = root.querySelector("#community-search"); + var status = root.querySelector("#community-status"); + var override = root.querySelector("#community-override"); + var count = root.querySelector("[data-community-count]"); + var page = root.closest(".md-content") || document; + var cards = Array.prototype.slice.call( + page.querySelectorAll("[data-community-card]") + ); + var empty = page.querySelector("[data-community-empty]"); + var clearButtons = page.querySelectorAll("[data-community-clear]"); + + if (!search || !status || !override || !count || !empty || !cards.length) { + return; + } + + root.dataset.communityReady = "true"; + + function updateUrl(query) { + if (!window.history || !window.history.replaceState) { + return; + } + var url = new URL(window.location.href); + if (query) { + url.searchParams.set("q", query); + } else { + url.searchParams.delete("q"); + } + window.history.replaceState(null, "", url); + } + + function applyFilters(options) { + var query = normalize(search.value); + var selectedStatus = status.value; + var requireOverride = override.checked; + var visible = 0; + + cards.forEach(function (card) { + var matchesQuery = + !query || normalize(card.dataset.communitySearch).indexOf(query) !== -1; + var matchesStatus = + !selectedStatus || card.dataset.communityStatus === selectedStatus; + var matchesOverride = + !requireOverride || card.dataset.communityOverride === "true"; + var show = matchesQuery && matchesStatus && matchesOverride; + card.hidden = !show; + if (show) { + visible += 1; + } + }); + + empty.hidden = visible !== 0; + count.textContent = + "Showing " + visible + " " + (visible === 1 ? "community" : "communities"); + if (!options || options.updateUrl !== false) { + updateUrl(search.value.trim()); + } + } + + function clearFilters() { + search.value = ""; + status.value = ""; + override.checked = false; + applyFilters(); + search.focus(); + } + + search.addEventListener("input", applyFilters); + status.addEventListener("change", applyFilters); + override.addEventListener("change", applyFilters); + clearButtons.forEach(function (button) { + button.addEventListener("click", clearFilters); + }); + + var initialQuery = new URL(window.location.href).searchParams.get("q"); + if (initialQuery) { + search.value = initialQuery; + } + applyFilters({ updateUrl: false }); + } + + function initialize() { + document + .querySelectorAll("[data-community-directory]") + .forEach(initializeDirectory); + } + + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", initialize, { once: true }); + } else { + initialize(); + } + + if (typeof window.document$ !== "undefined") { + window.document$.subscribe(initialize); + } +})(); diff --git a/docs/assets/regions/modules/configurator-support.js b/docs/assets/regions/modules/configurator-support.js new file mode 100644 index 0000000..1d0977c --- /dev/null +++ b/docs/assets/regions/modules/configurator-support.js @@ -0,0 +1,63 @@ +(function (root, factory) { + "use strict"; + root.MeshCoreRegionConfiguratorSupport = factory(); +}(typeof globalThis !== "undefined" ? globalThis : this, function () { + "use strict"; + + function finiteCoordinate(value, minimum, maximum) { + var number = Number(String(value == null ? "" : value).trim()); + return Number.isFinite(number) && number >= minimum && number <= maximum ? number : null; + } + + function parseCoordinates(latitude, longitude) { + var lat = finiteCoordinate(latitude, -90, 90); + var lon = finiteCoordinate(longitude, -180, 180); + return lat === null || lon === null ? null : { lat: lat, lon: lon }; + } + + function normalizeText(value) { + return String(value == null ? "" : value).replace(/\s+/g, " ").trim(); + } + + function commissioningRecord(input) { + var paths = Array.isArray(input.paths) ? input.paths.map(normalizeText).filter(Boolean) : []; + var commands = Array.isArray(input.commands) ? input.commands.map(normalizeText).filter(Boolean) : []; + var lines = [ + "MeshCore Canada repeater commissioning summary", + "Generated: " + normalizeText(input.generatedAt), + "Location label: " + (normalizeText(input.locationLabel) || "Not recorded"), + "Home region: " + normalizeText(input.homeRegion), + "Firmware: " + normalizeText(input.firmware), + "Region budget: " + normalizeText(input.budget), + "", + "Forwarding paths:" + ]; + paths.forEach(function (path) { lines.push("- " + path); }); + lines.push("", "Commands:"); + commands.forEach(function (command) { lines.push(command); }); + lines.push( + "", + "Verification:", + "1. Run region before saving and compare every path above.", + "2. Run region save only after the paths and flood permissions are correct.", + "3. Run region again after saving.", + "", + "This summary omits exact coordinates, passwords, private keys, and device identifiers." + ); + return lines.join("\n") + "\n"; + } + + function safeFileStem(value) { + var stem = normalizeText(value).toLowerCase() + .replace(/[^a-z0-9]+/g, "-") + .replace(/^-+|-+$/g, "") + .slice(0, 48); + return stem || "repeater"; + } + + return { + commissioningRecord: commissioningRecord, + parseCoordinates: parseCoordinates, + safeFileStem: safeFileStem + }; +})); diff --git a/docs/assets/regions/regions.css b/docs/assets/regions/regions.css index 5197615..d5d4be1 100644 --- a/docs/assets/regions/regions.css +++ b/docs/assets/regions/regions.css @@ -24,7 +24,7 @@ body:has([data-mcc-regions]) .md-typeset h3 { color: #ffffff; } -body:has([data-mcc-regions]) .md-typeset a { +body:has([data-mcc-regions]) .md-main .md-typeset a { color: var(--md-typeset-a-color, #5488e8); } @@ -321,7 +321,8 @@ body:has([data-mcc-regions]) .md-typeset a { } .mcc-choice small { - color: var(--mcc-muted); + color: var(--mcc-text); + opacity: 1; } .mcc-chip-list { @@ -910,8 +911,10 @@ body:has([data-mcc-regions]) .md-typeset a { } .mcc-region-child small { - color: var(--mcc-muted); + color: var(--mcc-text); font-size: 0.72rem; + font-weight: 600; + opacity: 1; } .mcc-panel-header { @@ -1590,7 +1593,7 @@ body:has([data-mcc-regions]) .md-typeset a { } .mcc-region-table td { - color: var(--mcc-text); + color: #eaf3f1; background: #101d26; font-size: 0.76rem; } diff --git a/docs/assets/regions/regions.js b/docs/assets/regions/regions.js index 2b3cd9e..339973d 100644 --- a/docs/assets/regions/regions.js +++ b/docs/assets/regions/regions.js @@ -12,11 +12,40 @@ var lucidePromise = null; var activeMaps = []; var LUCIDE_SRC = new URL("vendor/lucide.js", assetBase).href; + var configuratorSupport = window.MeshCoreRegionConfiguratorSupport || {}; + var REQUEST_TIMEOUT_MS = 12000; + + function fetchWithTimeout(url, options, timeoutMs) { + var requestOptions = Object.assign({}, options || {}); + var upstreamSignal = requestOptions.signal; + var controller = typeof AbortController !== "undefined" ? new AbortController() : null; + var timer = null; + var abortFromUpstream = function () { + if (controller) controller.abort(); + }; + if (controller) { + requestOptions.signal = controller.signal; + timer = window.setTimeout(function () { controller.abort(); }, timeoutMs || REQUEST_TIMEOUT_MS); + if (upstreamSignal) { + if (upstreamSignal.aborted) controller.abort(); + else upstreamSignal.addEventListener("abort", abortFromUpstream, { once: true }); + } + } + return fetch(url, requestOptions).finally(function () { + if (timer) window.clearTimeout(timer); + if (upstreamSignal) upstreamSignal.removeEventListener("abort", abortFromUpstream); + }); + } - function fetchJsonAsset(filename, errorMessage) { - return fetch(new URL(filename, assetBase)).then(function (response) { + function fetchJsonAsset(filename, errorMessage, retrying) { + return fetchWithTimeout(new URL(filename, assetBase), {}, REQUEST_TIMEOUT_MS).then(function (response) { if (!response.ok) throw new Error(errorMessage); return response.json(); + }).catch(function (error) { + if (!retrying && error && error.name !== "AbortError") { + return fetchJsonAsset(filename, errorMessage, true); + } + throw error && error.name === "AbortError" ? new Error(errorMessage + " (request timed out)") : error; }); } @@ -59,19 +88,17 @@ } function loadData(mode) { - if (mode === "map") { - return Promise.all([loadCatalog(), loadDisplayPartition()]).then(function (loaded) { - return applyGeneratedPartition(loaded[0], loaded[1], null); - }); - } - if (mode === "config") { - return Promise.all([loadCatalog(), loadResolverPartition()]).then(function (loaded) { - return applyGeneratedPartition(loaded[0], null, loaded[1]); - }); - } + if (mode === "map" || mode === "config") return loadCatalog(); return loadCatalog(); } + function ensureResolverData(data) { + if (data.resolverRegions) return Promise.resolve(data); + return loadResolverPartition().then(function (partition) { + return applyGeneratedPartition(data, null, partition); + }); + } + function prepareCatalog(data) { if (data.__mccPrepared) return data; var suppliedAliases = data.aliases || {}; @@ -281,10 +308,18 @@ function copyText(text, button, resetLabel) { var feedback = function (copied) { if (!button) return; + var host = button.closest ? button.closest("[data-mcc-regions]") : null; + var live = host && host.querySelector("[data-mcc-copy-status]"); var originalHtml = button.dataset.originalHtml || button.innerHTML || resetLabel || "Copy"; button.dataset.originalHtml = originalHtml; button.classList.toggle("is-copied", copied); button.innerHTML = copied ? "Copied" : "Copy failed"; + if (live) { + live.textContent = ""; + window.setTimeout(function () { + live.textContent = copied ? "Copied to clipboard." : "Copy failed. Select and copy the command manually."; + }, 10); + } window.setTimeout(function () { button.classList.remove("is-copied"); button.innerHTML = button.dataset.originalHtml || resetLabel || "Copy"; @@ -501,6 +536,10 @@ return regionPageHref("map") + (params.toString() ? "?" + params.toString() : ""); } + function configHrefForState(state) { + var mapHref = new URL(mapHrefForState(state)); + return regionPageHref("config") + mapHref.search; + } function haversineKm(aLat, aLon, bLat, bLon) { var rad = function (d) { return d * Math.PI / 180; }; var dLat = rad(bLat - aLat); @@ -870,8 +909,12 @@ }; } - function geocoderCaSearch(query) { - return fetch("https://geocoder.ca/?locate=" + encodeURIComponent(query) + "&json=1") + function geocoderCaSearch(query, signal) { + return fetchWithTimeout( + "https://geocoder.ca/?locate=" + encodeURIComponent(query) + "&json=1", + { signal: signal }, + REQUEST_TIMEOUT_MS + ) .then(function (res) { if (!res.ok) throw new Error("Geocoding service error"); return res.json(); @@ -881,13 +924,16 @@ }); } - function nominatimSearch(params) { + function nominatimSearch(params, signal) { var url = "https://nominatim.openstreetmap.org/search?" + new URLSearchParams(Object.assign({ format: "json", limit: "1", addressdetails: "1" }, params)); - return fetch(url, { headers: { "Accept-Language": "en-CA,en" } }) + return fetchWithTimeout(url, { + headers: { "Accept-Language": "en-CA,en" }, + signal: signal + }, REQUEST_TIMEOUT_MS) .then(function (res) { if (!res.ok) throw new Error("Geocoding service error"); return res.json(); @@ -897,13 +943,17 @@ }); } - function geocodeCanadianPostal(postal) { - return nominatimSearch({ postalcode: postal.formatted, country: "ca" }).then(function (hit) { + function geocodeCanadianPostal(postal, signal) { + return nominatimSearch({ postalcode: postal.formatted, country: "ca" }, signal).then(function (hit) { if (hit) return hit; - return nominatimSearch({ q: postal.formatted, countrycodes: "ca" }); + return nominatimSearch({ q: postal.formatted, countrycodes: "ca" }, signal); }).then(function (hit) { if (hit) return hit; - return fetch("https://geocoder.ca/?locate=" + encodeURIComponent(postal.compact) + "&json=1") + return fetchWithTimeout( + "https://geocoder.ca/?locate=" + encodeURIComponent(postal.compact) + "&json=1", + { signal: signal }, + REQUEST_TIMEOUT_MS + ) .then(function (res) { if (!res.ok) throw new Error("Geocoding service error"); return res.json(); @@ -917,26 +967,33 @@ }); } - function geocode(data, query) { + function geocode(data, query, allowExternal, signal) { var localMatch = localGeocode(data, query); if (localMatch && localMatch.ambiguous) { return Promise.reject(new Error("That name matches more than one region (" + localMatch.choices.join("; ") + "). Add a province or postal code.")); } if (localMatch && localMatch.exactLocalMatch) return Promise.resolve(localMatch); + if (!allowExternal) { + if (localMatch) return Promise.resolve(localMatch); + return Promise.reject(new Error( + "No local region matched. Allow online place lookup, enter coordinates, or browse the region list." + )); + } var postal = parseCanadianPostalCode(query); var primaryLookup = postal - ? geocodeCanadianPostal(postal) - : nominatimSearch({ q: query, countrycodes: "ca" }).then(function (hit) { + ? geocodeCanadianPostal(postal, signal) + : nominatimSearch({ q: query, countrycodes: "ca" }, signal).then(function (hit) { if (hit) return hit; - return nominatimSearch({ q: query }); + return nominatimSearch({ q: query }, signal); }); return primaryLookup.catch(function () { - return geocoderCaSearch(postal ? postal.formatted : query).catch(function () { return null; }); + if (signal && signal.aborted) throw new DOMException("Request cancelled", "AbortError"); + return geocoderCaSearch(postal ? postal.formatted : query, signal).catch(function () { return null; }); }).then(function (hit) { if (hit) return hit; if (localMatch) return localMatch; - throw new Error("Location lookup is temporarily unavailable. Try an airport code or use the map."); + throw new Error("Online place lookup is unavailable. Enter coordinates or browse the region list."); }); } @@ -1160,10 +1217,10 @@ '
' + '
' + icon("usb") + '
USB serial
At the repeater
' + '
  1. Connect the repeater to a computer with a data-capable USB cable.
  2. ' + - '
  3. In desktop Chrome or Edge, open the MeshCore Flasher. For Gessaman\'s MQTT Observer firmware, use the MeshCore Observer Flasher instead.
  4. ' + + '
  5. In desktop Chrome or Edge, open the MeshCore Flasher. For Gessaman\'s MQTT Observer firmware, use the MeshCore Observer Flasher instead.
  6. ' + '
  7. Choose Console, then approve the repeater\'s serial or COM port when the browser asks.
' + '
' + icon("radio-tower") + '
Remote over LoRa
Through a companion radio
' + - '
  1. On a phone or computer, connect the official MeshCore app to your companion radio.
  2. ' + + '
    1. On a phone or computer, connect the official MeshCore app to your companion radio.
    2. ' + '
    3. Open Contacts, select the repeater, then choose Remote Management from its menu.
    4. ' + '
    5. Enter the repeater admin password, tap Log In, then open Command Line.
    ' + '

    If the repeater is missing, open Tools → Discover Nearby Nodes. If a wait timer appears, let it finish before logging in.

' + @@ -1186,7 +1243,7 @@ return ''; }).join("") + '
' + '' + - 'MeshCore command help ' + icon("external-link") + '' + + 'MeshCore command help ' + icon("external-link") + '' + '
' : '
' + '
Commands
' + @@ -1230,6 +1287,11 @@ scopeNotice + technicalDetails + resultBody + + '
' + + '

Commissioning record

Download or print a summary without exact coordinates, credentials, or device identifiers.

' + + '
' + + '
' + + '
' + (statusNotes ? '
' + statusNotes + "
" : "") + "
"; @@ -1244,6 +1306,18 @@ copyText(button.getAttribute("data-cmd") || "", button.querySelector("em"), "Copy"); }); }); + var downloadSummary = target.querySelector("[data-action='download-commissioning']"); + if (downloadSummary) { + downloadSummary.addEventListener("click", function () { + downloadCommissioningSummary(data, state, downloadSummary); + }); + } + var printSummary = target.querySelector("[data-action='print-commissioning']"); + if (printSummary) { + printSummary.addEventListener("click", function () { + printCommissioningSummary(data, state, printSummary); + }); + } refreshIcons(target); } @@ -1274,6 +1348,70 @@ .concat(["region", "region save", "region"]); } + function commissioningSummary(data, state) { + if (!state || !state.canGenerate || !state.resolution || !configuratorSupport.commissioningRecord) return null; + var rec = recommend(data, state.resolution, state.type, state.selectedMetros, state.selectedExternalPaths); + var commands = currentCommands(data, state); + if (!rec || !commands) return null; + var homeTag = state.resolution.primary.seed.tag; + return configuratorSupport.commissioningRecord({ + generatedAt: new Date().toISOString(), + locationLabel: labelFor(data, homeTag), + homeRegion: labelledPath(data, ancestryFor(data, homeTag)), + firmware: state.firmware === "1.16" ? "v1.16+" : "v" + state.firmware + ".x", + budget: rec.budget.tagCount + " / 32 tags, " + rec.budget.responseBytes + " / 172 bytes", + paths: rec.paths.map(function (path) { return labelledPath(data, path); }), + commands: commands + }); + } + + function announceAction(button, message) { + var host = button && button.closest ? button.closest("[data-mcc-regions]") : null; + var live = host && host.querySelector("[data-mcc-copy-status]"); + if (!live) return; + live.textContent = ""; + window.setTimeout(function () { live.textContent = message; }, 10); + } + + function downloadCommissioningSummary(data, state, button) { + var summary = commissioningSummary(data, state); + if (!summary) return; + var homeTag = state.resolution.primary.seed.tag; + var stem = configuratorSupport.safeFileStem + ? configuratorSupport.safeFileStem(homeTag) + : "repeater"; + var blob = new Blob([summary], { type: "text/plain;charset=utf-8" }); + var url = URL.createObjectURL(blob); + var link = document.createElement("a"); + link.href = url; + link.download = "meshcore-" + stem + "-commissioning.txt"; + document.body.appendChild(link); + link.click(); + link.remove(); + window.setTimeout(function () { URL.revokeObjectURL(url); }, 0); + announceAction(button, "Commissioning summary downloaded. Exact coordinates and credentials were omitted."); + } + + function printCommissioningSummary(data, state, button) { + var summary = commissioningSummary(data, state); + if (!summary) return; + var popup = window.open("", "_blank", "noopener,noreferrer"); + if (!popup) { + announceAction(button, "The print window was blocked. Download the summary instead."); + return; + } + popup.opener = null; + popup.document.title = "MeshCore Canada commissioning summary"; + var heading = popup.document.createElement("h1"); + heading.textContent = "MeshCore Canada commissioning summary"; + var pre = popup.document.createElement("pre"); + pre.textContent = summary; + popup.document.body.appendChild(heading); + popup.document.body.appendChild(pre); + popup.focus(); + popup.print(); + announceAction(button, "Commissioning summary opened for printing. Exact coordinates and credentials were omitted."); + } function renderRegionDetail(data, section, target, state, tag) { if (!section || !target) return; if (!tag) { @@ -1322,7 +1460,7 @@ '
' + '' + (commands ? '' : '') + - (sourceUrl ? '' + icon("external-link") + 'Open source' : '') + + (sourceUrl ? '' + icon("external-link") + 'Open source' : '') + '
'; var copyTag = target.querySelector("[data-action='copy-detail-tag']"); @@ -1377,70 +1515,98 @@ function toolUi() { return '' + '
' + + '
' + '
    ' + - '
  1. ' + - '
  2. ' + - '
  3. ' + - '
  4. ' + + '
  5. ' + + '
  6. ' + + '
  7. ' + + '
  8. ' + '
' + '
' + '

Step 1 of 4

' + - '

Choose a location

' + - '' + + '

What are you configuring?

' + + '

We will recommend forwarding paths, then show how to apply them.

' + + '
' + + '' + + '' + + '' + + '
' + + '
' + + '
' + + '' + - '
Canada - Census cells · yellow means changed + Inspect cells or use the municipality list · yellow means changed
-
+
+

The map is optional. Use the municipality list in step three for a complete non-map path. In Inspect mode, select a cell to read its details. Enable the Advanced acknowledgement before painting individual cells.

+ diff --git a/docs/config/editor/infrastructure/draft-store.js b/docs/config/editor/infrastructure/draft-store.js new file mode 100644 index 0000000..76c1fd2 --- /dev/null +++ b/docs/config/editor/infrastructure/draft-store.js @@ -0,0 +1,137 @@ +import { + PROPOSAL_SCHEMA_V1, + PROPOSAL_SCHEMA_V2 +} from "../domain/proposal-types.js"; + +const DRAFT_PREFIX = "mcc-region-editor-draft"; +const DRAFT_VERSION = 1; +const HASH_RE = /^[0-9a-f]{64}$/; +const PROVINCE_RE = /^[0-9]{2}$/; +const DGUID_RE = /^[A-Za-z0-9-]{8,64}$/; + +function cleanText(value, maxLength) { + return typeof value === "string" ? value.slice(0, maxLength) : ""; +} + +function cleanChanges(value) { + if (!Array.isArray(value)) return []; + const seen = new Set(); + return value.flatMap((change) => { + if ( + !change || + typeof change !== "object" || + !DGUID_RE.test(String(change.DGUID || "")) || + typeof change.to !== "string" || + change.to.length > 29 || + seen.has(change.DGUID) + ) { + return []; + } + seen.add(change.DGUID); + return [{ DGUID: String(change.DGUID), to: String(change.to) }]; + }); +} + +export function draftKey(schema, authorityHash, province) { + if ( + ![PROPOSAL_SCHEMA_V1, PROPOSAL_SCHEMA_V2].includes(schema) || + !HASH_RE.test(String(authorityHash || "")) || + !PROVINCE_RE.test(String(province || "")) + ) { + return ""; + } + return [DRAFT_PREFIX, schema.endsWith("/v2") ? "v2" : "v1", authorityHash, province].join(":"); +} + +export function serializableDraft(value) { + const schema = value && value.schema; + const baseMembershipSha256 = String(value && value.baseMembershipSha256 || "").toLowerCase(); + const province = String(value && value.province || ""); + if (!draftKey(schema, baseMembershipSha256, province)) return null; + const draft = { + version: DRAFT_VERSION, + schema, + baseMembershipSha256, + province, + target: cleanText(value.target, 29), + reason: cleanText(value.reason, 1000), + changes: cleanChanges(value.changes), + savedAt: Number.isFinite(value.savedAt) ? value.savedAt : Date.now() + }; + if (schema === PROPOSAL_SCHEMA_V2) { + draft.newRegion = { + label: cleanText(value.newRegion && value.newRegion.label, 80), + tag: cleanText(value.newRegion && value.newRegion.tag, 29), + anchorDguid: DGUID_RE.test(String(value.newRegion && value.newRegion.anchorDguid || "")) + ? String(value.newRegion.anchorDguid) + : "" + }; + } + return draft; +} + +export function saveDraft(storage, value) { + const draft = serializableDraft(value); + if (!storage || !draft) return { ok: false, key: "", draft: null }; + const key = draftKey(draft.schema, draft.baseMembershipSha256, draft.province); + try { + storage.setItem(key, JSON.stringify(draft)); + return { ok: true, key, draft }; + } catch (_error) { + return { ok: false, key, draft }; + } +} + +export function loadDraft(storage, schema, authorityHash, province) { + const key = draftKey(schema, authorityHash, province); + if (!storage || !key) return null; + try { + const raw = storage.getItem(key); + if (!raw) return null; + const draft = serializableDraft(JSON.parse(raw)); + if ( + !draft || + draft.schema !== schema || + draft.baseMembershipSha256 !== authorityHash || + draft.province !== province + ) { + return null; + } + return draft; + } catch (_error) { + return null; + } +} + +export function removeDraft(storage, schema, authorityHash, province) { + const key = draftKey(schema, authorityHash, province); + if (!storage || !key) return false; + try { + storage.removeItem(key); + return true; + } catch (_error) { + return false; + } +} + +export function hasStaleDraft(storage, schema, authorityHash, province) { + if (!storage || !schema || !province) return false; + const schemaPart = schema.endsWith("/v2") ? "v2" : "v1"; + const prefix = [DRAFT_PREFIX, schemaPart, ""].join(":"); + try { + for (let index = 0; index < storage.length; index += 1) { + const key = storage.key(index); + if ( + typeof key === "string" && + key.startsWith(prefix) && + key.endsWith(`:${province}`) && + key !== draftKey(schema, authorityHash, province) + ) { + return true; + } + } + } catch (_error) { + return false; + } + return false; +} diff --git a/docs/config/index.md b/docs/config/index.md index a4564ec..bf02311 100644 --- a/docs/config/index.md +++ b/docs/config/index.md @@ -1,5 +1,23 @@ --- title: Set up repeater regions +description: Choose Canadian region paths and produce verified MeshCore repeater commands. +audience: + - repeater-operator +task: configure-repeater-regions +scope: canada-baseline +status: verified +owner: region-maintainers +last_reviewed: 2026-07-19 +review_by: 2026-10-19 +tested_with: + region_catalog: national-partition-2026-07-19 +difficulty: intermediate +estimated_time: 5-10 minutes +page_styles: + - assets/regions/regions.css +page_scripts: + - assets/regions/modules/configurator-support.js + - assets/regions/regions.js?v=20260719-2 hide: - navigation - toc diff --git a/docs/config/map.md b/docs/config/map.md index 02ec549..b13e844 100644 --- a/docs/config/map.md +++ b/docs/config/map.md @@ -1,5 +1,23 @@ --- title: Canadian region map +description: Find a Canadian region, inspect its hierarchy, and plan repeater paths without requiring the map. +audience: + - repeater-operator + - region-maintainer +task: explore-regions +scope: canada-baseline +status: verified +owner: region-maintainers +last_reviewed: 2026-07-19 +review_by: 2026-10-19 +tested_with: + region_catalog: national-partition-2026-07-19 +difficulty: intermediate +page_styles: + - assets/regions/regions.css +page_scripts: + - assets/regions/modules/configurator-support.js + - assets/regions/regions.js?v=20260719-2 hide: - toc --- diff --git a/docs/config/standard.md b/docs/config/standard.md index 442c117..723486d 100644 --- a/docs/config/standard.md +++ b/docs/config/standard.md @@ -1,5 +1,18 @@ --- title: Region definition and authority +description: The public rules, sources, authority, and change process for Canadian MeshCore regions. +audience: + - repeater-operator + - region-maintainer +task: understand-region-standard +scope: canada-baseline +status: verified +owner: region-maintainers +last_reviewed: 2026-07-19 +review_by: 2026-10-19 +tested_with: + region_schema: meshcore-canada-regions-v2 +difficulty: advanced --- # MeshCore Canada region definition and authority diff --git a/docs/contributing.md b/docs/contributing.md index 27e529c..830ae8b 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -1,75 +1,78 @@ -# How to Request Changes to MeshCore.ca - -Anyone can contribute an idea, correction, local observation, build note, or documentation request. You do not need to know how the website is built. - -## Easiest option: use the submission helper - -The [Share an Idea](submit-idea.md) page asks plain-language questions and prepares a structured submission for you. Review the result, complete the anti-spam check, and submit it directly without a GitHub account. You can also: +--- +title: Contribute to MeshCore Canada +description: Choose the simplest way to share an idea, update a community, correct a page, or contribute code. +audience: + - community-member + - documentation-contributor +task: choose-contribution +scope: canada-baseline +status: verified +owner: maintainers +author: MeshCore Canada maintainers +last_reviewed: 2026-07-19 +review_by: 2027-01-19 +tested_with: + issue_forms: f608cfe +difficulty: beginner +estimated_time: 2-15 minutes +--- -- copy it into the MeshCore Canada forum; -- paste it into the community Discord; or -- open the prepared form manually if you already use GitHub. +# Contribute to MeshCore Canada -The direct submission creates a public GitHub issue for maintainer review. No code, Git commands, radio settings, GitHub account, or perfect terminology are required. +Choose what you want to improve. You do not need to know Git or GitHub for the common paths. ---- +
-## GitHub issue templates +- **Share an idea** -If you are comfortable signing in to GitHub, use one of the repository's guided issue forms. + Report something missing, confusing, or difficult. The public form can create the issue without a GitHub account. -You do not need to know Git, GitHub, or how to edit the website. Just follow the steps below. + [:octicons-arrow-right-24: Share an idea](submit-idea.md) ---- +- **Add or update a community** -### 1. Go to the Issues page + Provide a community name, service area, status, contacts, language, and any local settings. -Visit the MeshCore Canada GitHub repository and click **Issues**. + [:octicons-arrow-right-24: Add a community](https://github.com/MeshCore-ca/MeshCore-Canada/issues/new?template=add_new_community.yml) + · [:octicons-arrow-right-24: Update a community](https://github.com/MeshCore-ca/MeshCore-Canada/issues/new?template=update_existing_community.yml) -Link: +- **Correct a page** ---- + Report a factual error, broken link, typo, or unclear instruction. -### 2. Click "New Issue" + [:octicons-arrow-right-24: Report a correction](https://github.com/MeshCore-ca/MeshCore-Canada/issues/new?template=fix_correction.yml) -Choose the template that matches what you want to do: +- **Propose a region change** -- **Add a New Community** -- **Update Existing Community** -- **Fix a Mistake / Small Correction** -- **Share a Community Idea** -- **General Request** + Use the boundary editor to prepare an existing-boundary move or a new-region proposal for public review. ---- + [:octicons-arrow-right-24: Open the region editor](config/editor/index.html) -### 3. Fill out the form +- **Write or improve a guide** -Provide as much detail as possible. + Start with a public request so maintainers can confirm scope, evidence, and the right owner before a large edit. ---- + [:octicons-arrow-right-24: Open a documentation request](https://github.com/MeshCore-ca/MeshCore-Canada/issues/new?template=general_request.yml) -### 4. Submit the issue +- **Contribute code** -A maintainer will: + Work from an issue, keep generated `site/` output out of commits, and include the checks relevant to your change. -1. Review the request -2. Make the change -3. Close the issue when complete + [:octicons-mark-github-24: View the repository](https://github.com/MeshCore-ca/MeshCore-Canada) -If we need more information, we’ll comment on the ticket. +
---- +## What happens next -## Advanced contributions +Submissions are public. A maintainer checks the scope and evidence, asks questions when needed, and records the outcome on the issue or pull request. -If you know GitHub and want to edit the markdown files directly: +!!! warning "Do not share secrets" + Remove passwords, access tokens, private keys, exact private coordinates, private addresses, and unredacted command output before submitting anything. -1. Fork the repo -2. Edit the relevant file in `docs/provinces/` -3. Open a pull request +## If you already use GitHub ---- +Use the repository's [guided issue forms](https://github.com/MeshCore-ca/MeshCore-Canada/issues/new/choose) or edit the relevant source page and open a pull request. Keep each change focused and state how you verified it. -## Thank you +## Need help choosing? -MeshCore.ca is a community-maintained directory, and contributions from all regions help keep the network growing across Canada. +Use [Share an Idea](submit-idea.md). Maintainers can route it to the right workflow. diff --git a/docs/hardware/overview.md b/docs/hardware/overview.md index 953f203..84019e4 100644 --- a/docs/hardware/overview.md +++ b/docs/hardware/overview.md @@ -1,34 +1,110 @@ -# Hardware Overview +--- +title: Choose MeshCore hardware +description: Choose a companion, repeater, antenna, or build path without treating local field notes as universal proof. +audience: + - newcomer + - node-builder +task: choose-hardware +scope: canada-baseline +status: draft +owner: docs-hardware +last_reviewed: 2026-07-19 +review_by: 2026-10-17 +difficulty: beginner +estimated_time: 5-10 minutes +destructive: false +page_styles: + - assets/styles/devices-builds.css +--- -Welcome! This section helps you pick the right hardware for MeshCore, whether you’re just joining the mesh or you’re ready to help it grow. +# Choose MeshCore Hardware -## Local Store Options +Choose the device role first. Then confirm the exact board and firmware target in the current official flasher before buying parts. -- One of our own members recently created a store with inventory in Ottawa to help the community grow, with local delivery by courier options. Visit them at: **[Space Hedgehog](https://space-hedgehog.com/)** -- New local store selling lithium batteries and related hardware, has shipping and free local Dymon locker (Nepean) pickup options: **[Motion Power & Witt Supply Co.](https://mpandw.ca/)** +
+**Outcome:** leave this page with one device path and a short list of details to verify. Product links are community leads, not guarantees of stock, compatibility, or performance. +
-## Companions (Connect to the Mesh) -Start here to grab a companion and connect to the mesh: +
-[Recommended Companions](recommended-companions.md) +**Draft recommendation set.** The device lists were migrated from Ottawa community notes on July 19, 2026. A hardware maintainer has not yet rechecked every product revision, price, connector, or firmware target. -These companions have been tried and tested by the Ottawa community and other meshes around the world. No need to reinvent the wheel, we have already done the testing. +
-!!! warning "Upgrade the companion antenna" - The included antenna performs poorly on all of these models. Plan to replace it, and upgrade to at least the Gizont on companions that support changing the antenna. - See: [Recommended Antennas](recommended-antenna.md) +## Pick the role you need ---- +
+
+

Personal companion

+

Use this to send and receive messages. Most companions pair with a phone; some have their own screen and controls.

+ Compare companions +
+
+

Repeater

+

Use this to improve coverage. Plan the complete system: radio, power, antenna, feed line, enclosure, mount, access, and recovery.

+ Compare repeater paths +
+
+

Antenna and cable

+

Match the radio band and connector before ordering. SMA and RP-SMA can look similar and are not interchangeable.

+ Check antenna notes +
+
+

DIY solar build

+

Choose this only if you can verify polarity, electrical limits, weather sealing, safe mounting, and physical recovery.

+ Choose a build +
+
+ +## Before you buy + +
    +
  • The exact board appears as the intended role in the current official MeshCore web flasher.
  • +
  • The device is for the Canadian 902–928 MHz band and not a different regional radio band.
  • +
  • The antenna connector, pigtail, and feed-line connectors match without forcing or improvised adapters.
  • +
  • The battery, charger, enclosure, and temperature range are supported by their manufacturers for the planned installation.
  • +
  • You have a known-good data USB cable and a practical way to recover the device physically.
  • +
  • A dated product page or manufacturer document supports every detail that matters to your purchase.
  • +
+ +!!! warning "Local field notes are not Canada-wide proof" + Ottawa purchasing leads and community observations can help form a shortlist. They do not establish nationwide availability, electrical safety, RF performance, firmware support, or suitability for a particular site. + +## Continue by task + +
+
+

Start with a companion

+

Compare phone requirements, controls, connector details, and accessories.

+ Companion guide +
+
+

Plan a repeater

+

Compare pre-built and DIY paths, then bench-test before mounting.

+ Repeater guide +
+
+

Install safely

+

Review property permission, structural limits, weather, cable routing, and inspection access.

+ Mounting guide +
+
+ +## Local purchasing leads + +These Ottawa-area links were in the previous guide. They are retained as purchasing leads only; check stock, specifications, return policy, shipping, and current pricing yourself. + +- [Space Hedgehog](https://space-hedgehog.com/) +- [Motion Power & Witt Supply Co.](https://mpandw.ca/) -## Repeaters (Expand coverage) -Repeaters are how we extend reach, fill dead zones, and make the network more reliable for everyone. Once you want to get started with helping the mesh expand or provide better coverage in your area, check out the recommended repeater hardware here: +## Next step -[Recommended Repeaters](recommended-repeaters.md) +After choosing a supported device, follow the matching safe flashing path: -These repeaters have been tried and tested by the Ottawa community and other meshes around the world. No need to reinvent the wheel, we have already done the testing. +- [Flash a companion](../meshcore/flash-companion.md) +- [Flash and bench-test a repeater](../meshcore/flash-repeater.md) +- [Flash a room server](../meshcore/flash-room-server.md) -!!! warning "Upgrade the repeater antenna" - The included antenna performs poorly on all of these models. Plan to replace it, and upgrade to at least the ALFA 5.8 dBi. - See: [Recommended Antennas](recommended-antenna.md) +Found an outdated product or compatibility detail? [Share the correction](../submit-idea.md) and include the manufacturer source and date you checked it. diff --git a/docs/hardware/recommended-antenna.md b/docs/hardware/recommended-antenna.md index 75fdd3f..8f4dbee 100644 --- a/docs/hardware/recommended-antenna.md +++ b/docs/hardware/recommended-antenna.md @@ -1,51 +1,105 @@ -# Antenna Recommendation +--- +title: Compare antennas and feed lines +description: Shortlist 915 MHz antennas and cables while checking connector fit, evidence, site needs, and dated pricing. +audience: + - companion-owner + - repeater-builder +task: choose-antenna +scope: ottawa-field-practice +status: draft +owner: docs-hardware +last_reviewed: 2026-07-19 +review_by: 2026-10-17 +difficulty: intermediate +estimated_time: 10-15 minutes +destructive: false +requires: + - confirmed-radio-band + - confirmed-device-connector +page_styles: + - assets/styles/devices-builds.css +--- -Most LoRa devices ship with a very basic factory antenna that performs poorly. -The Ottawa mesh community has tested many replacements, and the antennas below are highly recommended as reliable upgrades. +# Compare Antennas and Feed Lines -!!! warning "Swapping Antennas" - Make sure your device is disconnected to power/battery when swapping an antenna. Since these devices can transmit radio signals, turning on a device without an antenna can damage it. [See more information here.](https://electronics.stackexchange.com/questions/335912/can-i-break-a-radio-tranceiving-device-by-operating-it-with-no-antenna-connected) +Build a shortlist, then verify the radio band, connector, dimensions, mounting method, and current manufacturer specifications before ordering. -## Companion Antennas +
-These are SMA antennas and are more compact, yet they’ve consistently shown excellent performance in Ottawa and other meshes. We recommend any of the options listed here. +**Draft Ottawa field notes.** The products and legacy prices below were carried forward from community notes. No controlled comparison, link-health review, or product-revision audit was attached. Treat them as leads, not verified MeshCore Canada recommendations. -!!! warning "SMA vs. RP-SMA" - Pay close attention to what connection type a Companion/Repeater has since some come with Reverse Polarity SMA (RP-SMA). You will need an adapter to connect your SMA antenna or you will need to buy a RP-SMA antenna. - [More information on the differences between these connections.](https://blog.linitx.com/what-are-sma-rp-sma-connectors-and-whats-the-difference/) +
-| Product | Connector | Cost (CAD) | Link | -|-----------------------------|-----------|------------|------| -| Gizont 167CM 915MHz SMA M | SMA | $12 | [Space Hedgehog (Local Store)](https://space-hedgehog.com/products/gizont-915mhz-antenna?variant=51602989711416) | -| Gizont 167CM 915MHz SMA M | SMA | $10.53 | [AliExpress](https://www.aliexpress.com/item/1005004607615001.html) | -| Gizont 167CM 915MHz RP-SMA M | RP-SMA | $10.53 | [AliExpress](https://www.aliexpress.com/item/1005004607615001.html) | -| LINX ANT-916-CW-HW-SMA | SMA | $14.65 | [DigiKey](https://www.digikey.ca/en/products/detail/te-connectivity-linx/ANT-916-CW-HW-SMA/2694126?s=N4IgTCBcDaIDIEkByANABAQSQFQLQE4BGANlwGEB1XACSoGUBZDEAXQF8g) | -| Taoglas TI.09.A.0111 | SMA | $17.47 | [DigiKey](https://www.digikey.ca/en/products/detail/taoglas-limited/TI-09-A-0111/2332695?s=N4IgTCBcDaICoEMD2BzANggzgAjgSQDoAGATgIEFiBGGkAXQF8g) | -| Seeed Studio LoRa Antenna Kit | SMA | $6.79 | [Seeed Studio](https://www.seeedstudio.com/LoRa-Antenna-Kit-for-reTerminal-DM-p-5714.html) | +!!! danger "Disconnect power before changing an antenna" + Do not power or transmit from a radio without the correct antenna attached. Disconnect USB and battery power before connecting or removing an antenna, and follow the radio manufacturer's instructions. -## Repeater Omni Antennas +## Check compatibility first -These are N-Type antennas and are best suited for repeaters. At an absolute minimum, all repeaters should use the Alfa antenna — it’s a major reason the Ottawa mesh performs as well as it does. MrAlders0n has made a link between a repeater and a companion at 110KM distance with an Alfa on both ends. +
    +
  • The antenna is specified for the Canadian 902–928 MHz band.
  • +
  • The connector family and polarity match: SMA and RP-SMA can look similar but do not mate electrically in the same way.
  • +
  • The connector gender, pigtail, and feed line form one complete path.
  • +
  • The device and mount can support the antenna's size, weight, wind load, and cable strain.
  • +
  • Outdoor connectors can be weatherproofed and inspected without trapping water.
  • +
  • The current product page and datasheet support the details used in your decision.
  • +
-If you want something larger and higher-performing, we’ve tested the Seeed 1300 mm fiberglass antenna with excellent results. Please note that it is 1.3 metres long. We only recommend this antenna for repeaters installed at significant height (around 30 m AGL or higher) and intended for long-distance links or backbone use. +## Companion antenna leads -| Product | Connector | Cost (CAD) | Link | -|-----------------------------|-----------|------------|------| -| Alfa AOA-915-5ACM | N-Type | $34.99 | [Amazon](https://a.co/d/ieEIQpy) | -| Seeed Studio RF Explorer 902-928MHz 8dBi; 1300mm | N-Type | $110 | [Mouser](https://www.mouser.ca/ProductDetail/Seeed-Studio/318020693?qs=By6Nw2ByBD0kjpJjgHd0aQ%3D%3D) | +These portable antenna leads use SMA-family connectors. Confirm the exact connector on your device before ordering. -## Repeater Directional Antennas +
-Directional antennas are intended for fixed repeaters and long-distance point-to-point or point-to-multipoint links. All antennas listed here use N-Type connectors and are suitable for permanent outdoor installations. +| Product lead | Connector recorded in prior notes | Evidence available here | Legacy CAD figure | Source | +|---|---|---|---:|---| +| Gizont 167 cm 915 MHz | SMA male | Ottawa community field note; no test record attached | $10.53–$12 | [Space Hedgehog](https://space-hedgehog.com/products/gizont-915mhz-antenna?variant=51602989711416) / [AliExpress](https://www.aliexpress.com/item/1005004607615001.html) | +| Gizont 167 cm 915 MHz | RP-SMA male | Ottawa community field note; no test record attached | $10.53 | [AliExpress](https://www.aliexpress.com/item/1005004607615001.html) | +| LINX ANT-916-CW-HW-SMA | SMA | Product lead only | $14.65 | [DigiKey](https://www.digikey.ca/en/products/detail/te-connectivity-linx/ANT-916-CW-HW-SMA/2694126?s=N4IgTCBcDaIDIEkByANABAQSQFQLQE4BGANlwGEB1XACSoGUBZDEAXQF8g) | +| Taoglas TI.09.A.0111 | SMA | Product lead only | $17.47 | [DigiKey](https://www.digikey.ca/en/products/detail/taoglas-limited/TI-09-A-0111/2332695?s=N4IgTCBcDaICoEMD2BzANggzgAjgSQDoAGATgIEFiBGGkAXQF8g) | +| Seeed Studio LoRa Antenna Kit | SMA | Product lead only | $6.79 | [Seeed Studio](https://www.seeedstudio.com/LoRa-Antenna-Kit-for-reTerminal-DM-p-5714.html) | -| Product | Connector | Cost (CAD) | Link | -|------------------------|-----------|------------|------| -| L-com HG913Y-NF | N-Type | $237.17 | [DigiKey](https://www.digikey.ca/en/products/detail/l-com/HG913Y-NF/21289980) | +
-## Antenna Cables +## Fixed repeater antenna leads -For short, high-quality LMR-240 cables, [Infinite Cables](https://www.infinitecables.com/) in Toronto is the best source we’ve found. Their cables are on the expensive side, but the build quality is excellent and they offer a wide variety of lengths and connector combinations to suit any installation. +A permanent repeater antenna is a complete installation decision, not just a gain number. Include feed-line loss, connector count, pattern, local RF conditions, structure, lightning/grounding review, weather, and safe access. -| Product | Connector | Link | -|------------------------|-----------|------| -| LMR-240 Ultra Flex N-Type Male to N-Type Female | N-Type M to N-Type F | [Infinite Cables](https://www.infinitecables.com/products/lmr-240-ultra-flex-n-type-male-to-n-type-female-cable?variant=42809804980465) | +
+ +| Product lead | Type recorded in prior notes | Connector | Evidence available here | Legacy CAD figure | Source | +|---|---|---|---|---:|---| +| Alfa AOA-915-5ACM | Omnidirectional | N-type | Ottawa community field note; no controlled result attached | $34.99 | [Amazon Canada](https://www.amazon.ca/dp/B08H8J6ZV6) | +| Seeed Studio 318020693 | 902–928 MHz fiberglass omni, 1300 mm | N-type | Product lead and Ottawa field note | $110 | [Mouser](https://www.mouser.ca/ProductDetail/Seeed-Studio/318020693?qs=By6Nw2ByBD0kjpJjgHd0aQ%3D%3D) | +| L-com HG913Y-NF | Directional | N-type | Product lead only | $237.17 | [DigiKey](https://www.digikey.ca/en/products/detail/l-com/HG913Y-NF/21289980) | + +
+ +

Price snapshot date was not recorded in the legacy page. Recheck every figure, shipping cost, duty, connector, and revision at the linked source.

+ +## Feed-line lead + +The previous guide pointed to [Infinite Cables](https://www.infinitecables.com/) in Toronto for short LMR-240 Ultra Flex assemblies. The linked example was an [N-type male to N-type female cable](https://www.infinitecables.com/products/lmr-240-ultra-flex-n-type-male-to-n-type-female-cable?variant=42809804980465). Confirm both end connectors, length, loss, weather rating, and bend/strain requirements for your installation. + +## Record the decision + +Before installation, record: + +- antenna product and revision; +- published band and pattern; +- every connector and adapter in order; +- cable type and length; +- mounting and weatherproofing method; +- source links and the date checked; and +- the local test you will use after installation. + +## Verify after installation + +With the enclosure still accessible, confirm the radio reports its expected settings, the feed line is not loose or sharply bent, the weather seal does not create a water path, and a local message test succeeds. Do not attribute a change in coverage to the antenna alone without repeatable before/after evidence. + +## Human review required + +A hardware/RF reviewer must attach dated datasheets, connector verification, link checks, and repeatable evidence before any product on this page can carry a verified recommendation label. + +## Next step + +For a fixed installation, continue to [mounting options](repeater-mounting-options.md). For a portable node, return to [companion choices](recommended-companions.md). diff --git a/docs/hardware/recommended-companions.md b/docs/hardware/recommended-companions.md index ee09468..60772b5 100644 --- a/docs/hardware/recommended-companions.md +++ b/docs/hardware/recommended-companions.md @@ -1,78 +1,118 @@ -# Companion Nodes +--- +title: Compare companion devices +description: Compare phone-paired, standalone, and DIY companion paths with explicit accessories and verification limits. +audience: + - newcomer + - companion-owner +task: choose-companion +scope: canada-baseline +status: draft +owner: docs-hardware +last_reviewed: 2026-07-19 +review_by: 2026-10-17 +difficulty: beginner +estimated_time: 10-15 minutes +destructive: false +page_styles: + - assets/styles/devices-builds.css +--- -Companion nodes run dedicated companion firmware and operate as user endpoints on the MeshCore/Meshtastic network. -Most companion nodes pair with your smartphone over BLE to provide access to the mesh. +# Compare Companion Devices -There are also standalone companion nodes with built-in screens and input devices. These operate without a smartphone but still function as endpoints. +A companion is a personal MeshCore endpoint. Choose between a phone-paired radio, a standalone device, or a DIY build, then confirm the exact board in the current official flasher. -!!! warning "Upgrade the companion antenna" - The included antenna performs poorly on all of these models. Plan to replace it, and upgrade to at least the Gizont on companions that support changing the antenna. - See: [Recommended Antennas](recommended-antenna.md) +
-## Bluetooth Low Energy (BLE) Companions +**Draft product inventory.** These are community purchasing leads, not a verified compatibility list. Product revisions, connectors, batteries, included accessories, firmware targets, prices, and stock can change without notice. -These devices require a smartphone and the MeshCore or Meshtastic app. They connect to your phone over BLE, and you use the app to interact with the mesh. In this setup, the companion acts only as the radio, linking your phone to the mesh network. +
-### Pre-Built +## Choose the interaction style -The easiest way to get started is to buy a companion node, flash it with MeshCore/Meshtastic, and join the mesh. +
+
+

Phone-paired

+

Usually the simplest path. Your phone provides the main interface and connects to the radio over Bluetooth Low Energy (BLE).

+

Verify: phone platform, current app support, exact flasher target, battery, and antenna connector.

+
+
+

Standalone

+

Uses built-in controls and a display. It avoids a phone for normal use but may have a more specialized interface and firmware path.

+

Verify: current MeshCore support for the exact model and whether the intended controls work in that build.

+
+
+

DIY phone-paired

+

Lets you choose the board, battery, enclosure, pigtail, and antenna. It also makes you responsible for connector and electrical compatibility.

+

Verify: board revision, polarity, protected battery, enclosure fit, and complete antenna path.

+
+
-The MeshCore/Meshtastic app connects to the node over Bluetooth (BLE) and is used to send and receive messages on the mesh. +## Phone-paired product leads -!!! warning "Important ThinkNode M1 Note" - Make sure to order an **RP-SMA Antenna** with the device. - **Do not accidentally buy SMA — you specifically need RP-SMA.** - ThinkNode uses RP-SMA for the ThinkNode M1 for some reason +
-The following pre-built companion nodes are popular and widely available: +| Product lead | Phone needed | Screen / controls recorded in prior notes | Antenna detail to verify | Firmware evidence on this page | Source | +|---|---:|---|---|---|---| +| ThinkNode M1 | Yes | 1.54-inch screen and GPS listed by vendor | Prior notes say RP-SMA; confirm current revision | None attached; confirm exact target in official flasher | [Elecrow](https://www.elecrow.com/thinknode-m1-meshtastic-lora-signal-transceiver-powered-by-nrf52840-with-154-screen-support-gps.html) | +| LilyGO T-Echo | Yes | Display and GPS listed by vendor | Confirm current connector and included antenna | None attached; confirm exact target in official flasher | [LilyGO](https://lilygo.cc/products/t-echo-lilygo) | +| SenseCAP T1000-E | Yes | Card-style enclosure; no general-purpose controls listed here | Internal antenna recorded; confirm limitations with vendor | None attached; confirm exact target in official flasher | [Seeed Studio](https://www.seeedstudio.com/SenseCAP-Card-Tracker-T1000-E-for-Meshtastic-p-5913.html) | +| RAK WisMesh Tag | Yes | GPS and integrated enclosure recorded in prior notes | Integrated antenna recorded; confirm revision | None attached; confirm exact target in official flasher | [AliExpress listing](https://www.aliexpress.com/item/1005009754254701.html) | -!!! warning "Aliexpress bundles" - Aliexpress usually shows the cheapest items (e.g., only the GPS module) when opening their links. Make sure you select the right bundle when adding to your cart. - +
-| Product | Notes | Link | -|--------------------|-------|------| -| **ThinkNode M1** | Compact device powered by the nRF52840 with a 1.54" screen and GPS support. Designed as a ready-to-use companion node for reliable messaging and tracking. **Note:** Has RP-SMA connector - See SMA vs. RP-SMA warning above. | [Elecrow](https://www.elecrow.com/thinknode-m1-meshtastic-lora-signal-transceiver-powered-by-nrf52840-with-154-screen-support-gps.html) | -| **LilyGO T-Echo** | Compact device with onboard display and GPS. A solid ready-to-use option with minimal setup required. **Note:** Buy the non-flashed version; it’s cheaper and easy to flash MeshCore/Meshtastic using the web flasher. | [LilyGO Store](https://lilygo.cc/products/t-echo-lilygo) | -| **SenseCAP T1000-E** | Slim card-style tracker device from SeeedStudio. Portable and IP65-rated. **Note:** Range is more limited due to internal antennas. | [SeeedStudio](https://www.seeedstudio.com/SenseCAP-Card-Tracker-T1000-E-for-Meshtastic-p-5913.html) | -| **RAK WisMesh Tag** | Rugged device with GPS, integrated antennas, 1000mAh battery, and IP66 enclosure. Pre-flashed firmware for instant use. **Note:** Range is more limited due to internal antennas. | [AliExpress](https://www.aliexpress.com/item/1005009754254701.html) | +!!! warning "Marketplace bundles can change" + A product page may default to an accessory, GPS module, different radio band, or different connector. Confirm the exact bundle and model before checkout. ---- +## DIY example from Ottawa notes -### Build Your Own +This legacy example describes a phone-paired companion. It does not include a verified enclosure or a dated compatibility review. -For hobbyists who like to source parts and assemble their own node, here is an Ottawa-friendly example build (antenna not included; see [Recommended Antennas](recommended-antenna.md)). +
-This is a **companion node** role and requires a smartphone. -The MeshCore/Meshtastic app connects to the node over Bluetooth (BLE) and is used to send and receive messages on the mesh. +| Part | Product lead | Detail to verify | Legacy CAD figure | Source | +|---|---|---|---:|---| +| LoRa board | Heltec T114 bundle with screen | Exact board revision and official companion target | $45.99 | [AliExpress](https://www.aliexpress.com/item/1005007916299029.html) | +| Pigtail | Right-angle IPEX to SMA, 8 cm | IPEX generation, SMA gender/polarity, cable orientation | $4.67 | [AliExpress](https://www.aliexpress.com/item/1005009270132403.html) | +| Battery | MakerFocus 3.7 V 3000 mAh LiPo pack | Connector, polarity, protection, dimensions, charge limits | $34.34 | [MakerFocus](https://www.makerfocus.com/products/makerfocus-3-7v-3000mah-lithium-rechargeable-battery-1s-3c-lipo-battery-pack-of-4?variant=44823607541998) | +| Antenna | Gizont 167 cm 915 MHz SMA lead | Band and exact connector match | $10.68 | [AliExpress](https://www.aliexpress.com/item/1005004607615001.html) | -### Example DIY Build +
-| Item | Product Name | Cost (CAD) | Link | -|--------------|-------------------------------|------------|------| -| **LoRa Board** | Heltec T114 (Bundle with screen) | $45.99 | [AliExpress](https://www.aliexpress.com/item/1005007916299029.html)| -| **Right-angle IPEX to SMA Pigtail Cable** | SMA-KW 2PCS 8cm | $4.67 | [AliExpress](https://www.aliexpress.com/item/1005009270132403.html?)| -| **Battery** | Makerfocus 3.7V 3000mAh LiPo - (Pack of 4), Micro JST 1.5 connection with protection board | $34.34 | [MakerFocus](https://www.makerfocus.com/products/makerfocus-3-7v-3000mah-lithium-rechargeable-battery-1s-3c-lipo-battery-pack-of-4?variant=44823607541998) | -| **Antenna** | Gizont 167CM 915MHz SMA M |$10.68 | [AliExpress](https://www.aliexpress.com/item/1005004607615001.html) (Make sure you select the right antenna when opening the link) +

The legacy page estimated $95.68 CAD before enclosure and some shipping. Its price snapshot date was not recorded. Recalculate from current listings.

-*Approximate total cost:* **$95.68 CAD** -*Prices will vary and may include shipping costs, so please confirm with links. MakerFocus batteries are shipped from China with no duties.* +For an enclosure lead, the previous guide referenced [Alley Cat's printable models](https://www.printables.com/@AlleyCat/models). Confirm board, battery, pigtail, strain relief, and thermal fit for the exact model you print. -!!! warning "Case for Example DIY Build" - This DIY build example does not include a case. For 3D-printable cases, check out **[Alley Cat’s models](https://www.printables.com/@AlleyCat/models)** — they are excellent for custom companion node builds. Make sure the case you choose will fit the 3000 mAh battery and the right angle IPEX to SMA connector. +## Standalone product leads -If you are in the Ottawa area, you can also purchase this build fully assembled locally from [Space Hedgehog](https://space-hedgehog.com/). +
---- +| Product lead | Phone for normal messaging | Interface | What must be confirmed | Source | +|---|---:|---|---|---| +| LilyGO T-LORA Pager | No, if the intended standalone firmware supports the required workflow | Built-in display and controls | Exact revision, official target, role, and control support | [LilyGO](https://lilygo.cc/en-ca/products/t-lora-pager) | +| LilyGO T-Deck Plus | No, if the intended standalone firmware supports the required workflow | Display, keyboard, and trackball | Exact revision, official target, role, and current interface limitations | [LilyGO](https://lilygo.cc/products/t-deck-plus-meshtastic) | + +
+ +## Before buying + +
    +
  • The exact product revision appears in the current official MeshCore flasher for the companion role.
  • +
  • The radio is the Canadian 902–928 MHz variant.
  • +
  • The phone/app requirement matches how you intend to use it.
  • +
  • The battery connector, polarity, protection, dimensions, and charging method are documented.
  • +
  • The antenna and every adapter match the device connector.
  • +
  • The enclosure leaves the USB recovery path accessible.
  • +
  • You checked current price, stock, shipping, duty, and return terms.
  • +
+ +## Verify the completed setup + +After flashing, confirm the app or standalone interface reconnects, the intended role and radio settings survive a reboot, and a local message test succeeds. A product listing alone is not proof of firmware support. -## Standalone Nodes +## Human review required -There are standalone devices such as the **T-Deck**, but we recommend starting with a companion node instead. -Standalone units tend to be more expensive, the UI is not as smooth as the mobile app, and they still have quirks and firmware limitations that can make them challenging for beginners. +A hardware maintainer must attach dated manufacturer sources, firmware compatibility, connector checks, and a link-health review before any product on this page can carry a verified recommendation label. -### Available Standalone Devices +## Next step -| Product | Notes | Link | -|----------------------|-------|------| -| **LilyGO T-LORA Pager** | A compact standalone LoRa messaging device styled like a classic pager. Useful for simple off-grid communication without needing a smartphone. | [LilyGO Store](https://lilygo.cc/en-ca/products/t-lora-pager) | -| **LilyGO T-Deck Plus** | Updated version of the T-Deck with improved specs and refinements. Built with Meshtastic/MeshCore/Meshtastic in mind.
**However:** the built-in trackball is a major downside and many users dislike it. | [LilyGO Store](https://lilygo.cc/products/t-deck-plus-meshtastic) | +Once the exact board is confirmed, [flash a companion safely](../meshcore/flash-companion.md). If you meant to improve coverage for others, [compare repeater paths](recommended-repeaters.md) instead. diff --git a/docs/hardware/recommended-repeaters.md b/docs/hardware/recommended-repeaters.md index e64e8fc..e09a767 100644 --- a/docs/hardware/recommended-repeaters.md +++ b/docs/hardware/recommended-repeaters.md @@ -1,60 +1,114 @@ -# Repeaters +--- +title: Choose a repeater path +description: Compare a pre-built repeater with the 300 mW and 1 W community build paths as complete systems. +audience: + - repeater-builder + - network-operator +task: choose-repeater +scope: canada-baseline +status: draft +owner: docs-hardware +last_reviewed: 2026-07-19 +review_by: 2026-10-17 +difficulty: intermediate +estimated_time: 10-20 minutes +destructive: false +page_styles: + - assets/styles/devices-builds.css +--- -Repeaters form the stable backbone of the Ottawa MeshCore network. -You can choose between **pre-built repeaters** or **DIY builds**, and both are great options. +# Choose a Repeater Path -Pre-built units have improved a lot in reliability and price, while DIY builds remain popular for those who like full control over their hardware. +Plan a complete, recoverable repeater system. The radio board alone is not a deployable repeater. -!!! warning "Upgrade the repeater antenna" - The included antenna performs poorly on all of these models. Plan to replace it, and upgrade to at least the ALFA 5.8 dBi. - See: [Recommended Antennas](recommended-antenna.md) - ---- +
-## Important Note for nRF52-Based Repeaters +**Draft product and build guidance.** These paths were migrated from Ottawa community notes. Exact hardware revisions, electrical limits, firmware targets, prices, climate suitability, and performance have not all been independently reverified. -If you plan to use a **nRF52** board for a repeater, you must update it to the **OTAFIX bootloader firmware**. -Without this fix, if an OTA update fails over BLE, the repeater will enter an unusable state and require physical access to recover. +
-See more infomation [in the repeater flashing instructions page](../meshcore/flash-repeater.md). +## Choose a repeater path ---- +
+
+

Pre-built solar enclosure

+

Less mechanical assembly, but the listing may omit batteries, pigtail, or antenna.

+

Best fit: an operator who will verify every included part and keep USB recovery access.

+ Review the system lead +
+
+

300 mW community build

+

The simpler of the two documented DIY paths, using a RAK Unify enclosure and RAK WisBlock notes.

+

Best fit: an experienced builder willing to review the draft BOM and electrical path.

+ Open the draft build +
+
+

1 W experimental build

+

A specialized Ottawa backbone experiment with more parts, power, filtering, fabrication, and unverified optional telemetry.

+

Best fit: a reviewed network need and a builder able to validate every electrical and firmware detail.

+ Review the experimental build +
+
-## Pre-Built +!!! tip "Start with the network need, not transmit power" + Site, height, antenna system, feed-line loss, local noise, neighbouring regions, power budget, and maintenance access all affect a repeater. Coordinate with the local community before selecting a high-power or long-reach path. -These options come fully assembled; simply flash MeshCore and mount them in a high location to expand the mesh. +## Compare the complete systems -### Pre-Built Repeater Options +
-!!! warning "SenseCAP Solar Node P1 Notes" - To build out a SenseCAP Solar Node P1, you need to purchase the **4 items below**: the node itself, batteries, an upgraded antenna, and the correct pigtail cable. +| Path | Power system | Enclosure / mount | Antenna path | Remote management | Skill | Evidence here | +|---|---|---|---|---|---|---| +| SenseCAP Solar Node P1 lead | Listing-specific solar and battery bundle; verify exact contents | Vendor enclosure; verify revision, weather claim, and mount | Prior notes call for an external antenna and RP-SMA-to-N pigtail | Depends on exact supported board/firmware | Intermediate | Product and community leads only | +| 300 mW community build | Solar enclosure and protected battery notes | RAK Unify enclosure plus site-specific mount | External 915 MHz antenna and pigtail | Confirm current repeater firmware and access path | Intermediate | Legacy Ottawa build dated 2026-01-01; no attached test record | +| 1 W experimental build | Separate panel, manager, battery protection, and high-power radio notes | Fabricated junction-box system | External antenna, feed line, and filter notes | Confirm current repeater firmware and recovery | Advanced | Ottawa experiment dated 2026-01-01; optional telemetry unverified | -| Product | Notes | Link | -|---------|-------|------| -| **SenseCAP Solar Node P1 w/o GPS & Battery** | Solar-powered communication node using the XIAO nRF52840 Plus + Wio-SX1262 LoRa module. Includes a 5W solar panel, IPX5 waterproofing. | [RobotShop (Canadian Store)](https://ca.robotshop.com/products/sensecap-solar-node-p1-meshtastic-w-o-gps-battery) | -| **Batteries for SenseCAP Solar Node P1 (Local Store)** | Four button-top 18650 cells, required if you purchase the P1 model without GPS & Battery. | [Motion Power & Witt Supply Co.](https://mpandw.ca/products/button-top-eve-35v-house-made) | -| **LoRa Antenna for SenseCAP Solar Node P1** | External antenna for the node. | [Amazon.ca](https://www.amazon.ca/dp/B08H8J6ZV6?ref=ppx_yo2ov_dt_b_fed_asin_title) | -| **RP-SMA to N-Type Cable (Type 2, 30 cm) for SenseCAP Solar Node P1** | Pigtail cable for connecting the node to an external antenna. Make sure to select **Type 2 (RP-SMA)** and **30 cm** when ordering. | [AliExpress](https://www.aliexpress.com/item/1005004652556159.html) | +
---- +## Pre-built system lead -## Build Your Own +The previous guide described one SenseCAP Solar Node P1 configuration as **four separate purchases**. Treat them as a system checklist, not four independent repeater recommendations. -We have two DIY solar repeater builds. Choose based on the role the repeater will fill in the mesh. +
-### 300mW Solar Repeater (default) +| Required system item | Legacy product lead | Detail to verify | Source | +|---|---|---|---| +| Solar node | SenseCAP Solar Node P1 without GPS and battery | Exact Canadian-band revision, board, firmware target, enclosure rating, and included accessories | [RobotShop Canada](https://ca.robotshop.com/products/sensecap-solar-node-p1-meshtastic-w-o-gps-battery) | +| Batteries | Four button-top 18650 cells | Chemistry, protection, capacity, temperature, charger compatibility, and vendor instructions | [Motion Power & Witt Supply Co.](https://mpandw.ca/products/button-top-eve-35v-house-made) | +| Antenna | External 915 MHz lead | Band, connector, mount, and complete feed line | [Amazon Canada](https://www.amazon.ca/dp/B08H8J6ZV6) | +| Pigtail | RP-SMA to N-type, legacy note says Type 2 / 30 cm | Connector polarity, gender, loss, length, weather seal, and current listing option | [AliExpress](https://www.aliexpress.com/item/1005004652556159.html) | -Use this for the vast majority of deployments. It is cheaper, simpler to assemble, and fully sufficient for most rooftop, gutter, and pole-mounted locations across the city. +
-[300mW Solar Repeater Build Guide](./repeater-solar-300mw-diy-build.md) +

No price snapshot is shown because the legacy page did not record a complete-system price date. Recalculate the full system from current sources.

-### 1W Solar Repeater (backbone / long-reach) +## Before building or buying -Only build a 1W repeater when you have a specific reason to. Good reasons include backbone links between repeaters, and locations that have been proven through testing to need the extra output to reach far enough to connect to the rest of the mesh. Higher transmit power increases noise floor and battery draw. +
    +
  • The exact radio board appears in the current official MeshCore flasher for the repeater role.
  • +
  • The radio and antenna are for the Canadian 902–928 MHz band.
  • +
  • The full power chain follows manufacturer limits and uses documented protection.
  • +
  • The antenna is attached before the radio can transmit.
  • +
  • Property permission, structural review, weather loads, cable routing, and electrical hazards are addressed.
  • +
  • The repeater can be recovered by USB after installation.
  • +
  • The local region and settings are confirmed in the repeater configurator.
  • +
  • A bench commissioning record and maintenance interval will be stored with the site record.
  • +
-[1W Solar Repeater Build Guide](./repeater-solar-1w-diy-build.md) +## Firmware and bootloader gate -!!! tip "Not sure which to build?" - Start with the **300mW** build. Reach out to the community first if you think you need a 1W, since location and antenna choice usually matter more than transmit power. +If the chosen board is nRF52-based, read the board-specific [bootloader decision and USB flashing guide](../meshcore/flash-repeater.md#nrf52-bootloader-decision) before relying on over-the-air recovery. Do not copy a bootloader file from another board example. ---- +## Commission before mounting + +A completed repeater should remain on the bench until it survives a reboot, retains its settings, sends an advert received by a nearby companion, passes a local routing test, and can still be recovered over USB. Then use the [mounting safety checklist](repeater-mounting-options.md). + +## Human review required + +A hardware maintainer must attach dated product specifications, electrical review, firmware compatibility, and repeatable commissioning evidence before these paths can be labelled verified. + +## Next step + +- [Review the 300 mW draft build](repeater-solar-300mw-diy-build.md) +- [Review the experimental 1 W build](repeater-solar-1w-diy-build.md) +- [Flash and bench-test a repeater](../meshcore/flash-repeater.md) diff --git a/docs/hardware/repeater-mounting-options.md b/docs/hardware/repeater-mounting-options.md index 859bf6a..f1ae587 100644 --- a/docs/hardware/repeater-mounting-options.md +++ b/docs/hardware/repeater-mounting-options.md @@ -1,72 +1,119 @@ +--- +title: Plan a repeater mount +description: Compare community mounting examples while prioritizing permission, structural safety, weather, cable routing, and inspection. +audience: + - repeater-builder + - site-owner +task: plan-repeater-mount +scope: canada-baseline +status: draft +owner: docs-hardware +last_reviewed: 2026-07-19 +review_by: 2026-10-17 +difficulty: advanced +estimated_time: site dependent +destructive: false +requires: + - property-permission + - site-specific-safety-review +page_styles: + - assets/styles/devices-builds.css +--- -# Mounting Repeaters +# Plan a Repeater Mount -Mounting repeaters securely is critical for good performance and long-term reliability. -The goal is always to place the antenna as high as possible with clear line-of-sight, while ensuring the mount can withstand wind and weather. +Use these community examples to discuss a site. They are not engineered plans for a particular building, mast, climate, or property. -**Remember: Height is might — the higher the node, the better connection you will get to the mesh.** +
---- +**Draft installation reference.** The examples have not been reviewed for your structure, wind, ice, snow, lightning exposure, grounding, electrical clearance, roof access, or local requirements. -## Mounting Options -### Pole Mount -The most common method is using a standard pole and clamp system. +
-* Simple to set up with widely available hardware. -* Can be attached to an existing mast, tower, or sturdy pole. -* Best suited for permanent installations where height and stability are required. +!!! danger "Antenna height never overrides safety" + Do not work near overhead electrical lines or on an unsafe roof, ladder, mast, chimney, gutter, or vent. If the attachment, load, grounding, weather exposure, or access plan is uncertain, stop and use a qualified local installer. -Examples of pole mounts: +## Before you choose a mount -* [Amazon – Adjustable Pole Mount](https://a.co/d/5U5cT4m) -* [AliExpress – Pole Clamp Option 1](https://www.aliexpress.com/item/1005004943447000.html) -** [Mounting Guide](https://docs.rakwireless.com/product-categories/wisblock/rakbox-uo180x130x60/installation-guide/#mounting-guide) -* [AliExpress – Pole Clamp Option 2](https://www.aliexpress.com/item/1005004943650198.html) +
    +
  • The property owner and any required authority approve the location and cable route.
  • +
  • A qualified person has addressed structure, wind, ice, snow, corrosion, electrical clearance, lightning/grounding context, and safe access.
  • +
  • The repeater passed its bench commissioning test and remains recoverable.
  • +
  • The antenna, enclosure, feed line, connectors, strain relief, and drip paths are planned as one system.
  • +
  • An inspection and removal plan is recorded before installation.
  • +
-Examples of poles: +## What this installation changes -* [Princess Auto - 4ft Army Tent Pole](https://www.princessauto.com/en/4-ft-army-tent-pole/product/PA0009280777) -** Can be linked together to increase pole length (ensure proper anchoring) -* [3/4 x 36-inch steel tube](https://www.homedepot.ca/product/paulin-3-4-x-36-inch-round-steel-tube/1000126774) +A fixed mount adds load and penetrations or clamps to a property, exposes equipment and cables to weather, and can make physical recovery harder. Record who owns the site, who may access it, and how the installation will be inspected and removed. -**Pole mount Alfa 5.8dBi Antenna** +## Community examples -Stéphane P created this 3D print to allow pole mounting of the Alfa 5.8 dBI Antenna -![](images/PoleMount.jpg){ width="300" } +### Pole and clamp -* [Download STL](https://drive.google.com/file/d/1wIU9kLxolzM9vPUB35ETY1sCPLGvtfFu/view?usp=share_link) +A pole or purpose-built mast can provide a defined cable route and mounting point. Every pole section, clamp, anchor, structure, and load still needs site-specific review. -**Pole mount on chimney** +Community purchasing and design leads: -![](images/ChimneyMount.jpg){ width="300" } +- [Adjustable pole mount](https://a.co/d/5U5cT4m) +- [Pole clamp option 1](https://www.aliexpress.com/item/1005004943447000.html) +- [RAKwireless enclosure mounting guide](https://docs.rakwireless.com/product-categories/wisblock/rakbox-uo180x130x60/installation-guide/#mounting-guide) +- [Pole clamp option 2](https://www.aliexpress.com/item/1005004943650198.html) +- [Princess Auto 4 ft army tent pole](https://www.princessauto.com/en/4-ft-army-tent-pole/product/PA0009280777) +- [3/4 × 36-inch steel tube](https://www.homedepot.ca/product/paulin-3-4-x-36-inch-round-steel-tube/1000126774) ---- +Stéphane P shared a printable Alfa antenna mount: [download the STL](https://drive.google.com/file/d/1wIU9kLxolzM9vPUB35ETY1sCPLGvtfFu/view?usp=share_link). -### Gutter Mount +![A 3D-printed Alfa antenna mount attached to a pole](images/PoleMount.jpg){ .mc-build-photo loading=lazy } -Community member **Aussiemandias** designed a specialized 3D printable gutter mount for quick and discreet repeater installation. +![A community example of a repeater pole attached near a chimney](images/ChimneyMount.jpg){ .mc-build-photo loading=lazy } -* Ideal for residential areas where pole mounting isn’t practical. -* Attaches directly to house gutters without the need for drilling into walls. -* Provides a stable base while keeping the antenna elevated. +A chimney installation requires specific review of the chimney, structure, heat, clearances, fasteners, weather, and safe access. The photograph is not an instruction to copy the attachment. -![](images/RAKUnify_GutterMounted.jpeg){ width="300" } +### Gutter-mounted community example -* [Download STL](https://drive.proton.me/urls/A0P57SRHT0#voPRasptRVbW) +Aussiemandias shared a printable gutter mount: [download the STL](https://drive.proton.me/urls/A0P57SRHT0#voPRasptRVbW). ---- +![A RAK Unify enclosure installed with a community-designed gutter mount](images/RAKUnify_GutterMounted.jpeg){ .mc-build-photo loading=lazy } -### Vent Pipe Extension Mount +A gutter is not automatically structural. Inspect the gutter, fascia, fasteners, drainage, ice/snow loads, wind, cable route, and access before considering this approach. -* Standard 3 foot ABS pipe from Home Depot, 3" in diameter. -* ABS cement on the one side attached to the extension and on the side attached to the house two self-tapping screws in both sides as a safety measure, which allows removal in the future. +### Vent-pipe extension community example -![](images/VentPipeExtension.jpg){ width="300" } +The legacy note described a 3 ft, 3-inch ABS extension, ABS cement on one side, and self-tapping screws on the house side. Do not reproduce that detail without confirming the existing vent, building, material compatibility, code implications, and added load with a qualified person. ---- +![A community example of a repeater mounted on an ABS vent-pipe extension](images/VentPipeExtension.jpg){ .mc-build-photo loading=lazy } + +## Installation Checklist + +
    +
  • Permission, safe access, electrical clearance, and site-specific review are documented.
  • +
  • Mount, fasteners, structure, and cable route match the reviewed plan.
  • +
  • Outdoor hardware and dissimilar metals are addressed for local conditions.
  • +
  • Cables have strain relief, bend protection, a drip path, and sealed entries.
  • +
  • The enclosure can vent as designed and does not collect water.
  • +
  • The antenna is attached before the radio can transmit.
  • +
  • Removal and physical USB recovery remain practical.
  • +
+ +## Expected result and verification + +From a safe position, inspect the complete installation for movement, cable strain, loose hardware, blocked drainage, and water paths. Then confirm the repeater reconnects, retains its settings, sends an advert received locally, and passes the planned message-routing check. + +## Recovery and removal + +If anything moves, leaks, loosens, corrodes, changes electrically, or fails its radio check, stop using the site. Disconnect power when safe, remove or secure the equipment through the approved access plan, and return it to the bench. Do not troubleshoot energized equipment at height. + +## Maintenance record + +
+ +No Canada-wide inspection interval has been approved. Before deployment, record a site-specific interval and the conditions that trigger an extra inspection, such as severe weather, nearby work, water entry, or changed radio behaviour. + +Record each inspection date, inspector, photographs, hardware/cable condition, enclosure condition, radio verification, corrective work, and next due date. + +
-## Notes +## Next step -* Regardless of mount type, always ensure the mount and fasteners are weather-rated and can handle local wind conditions. -* Periodically inspect mounts for wear, corrosion, or loosening. -* These options are specifically for the recommended solar repeater build, linked here: [Building a Solar Node – Rak Unify Box](repeater-solar-300mw-diy-build.md). +Return to the [repeater choice](recommended-repeaters.md) or [flash and bench-test the repeater](../meshcore/flash-repeater.md) before installing it. diff --git a/docs/hardware/repeater-solar-1w-diy-build.md b/docs/hardware/repeater-solar-1w-diy-build.md index 4af5405..e6290c7 100644 --- a/docs/hardware/repeater-solar-1w-diy-build.md +++ b/docs/hardware/repeater-solar-1w-diy-build.md @@ -1,227 +1,233 @@ -# Building a 1W Solar Repeater – Ikoka Stick - -Authored By: MrAlders0n (Ottawa) -Date: 2026-01-01 - -!!! warning "No warranty" - This is to help the community understand how to make a repeater, and I by no means provide any warranty for anyone following this guide. Please test everything first with a multimeter and other tools before powering anything on. - -**Board:** GOME Ikoka Stick (HW v0.4.0) -**Power:** Waveshare Solar Power Manager (standard) -**Firmware:** MeshCore - -This guide walks through assembling a solar-powered MeshCore repeater using the GOME Ikoka Stick, a junction box enclosure, and an epoxied solar panel. Follow each step carefully for a reliable and weatherproof build. - -!!! info "When to use a 1W repeater" - 1W repeaters should typically be used for backbone links and for locations that are struggling to connect to the mesh. They are not necessary for every deployment. - -!!! danger "Antenna Required" - **Always ensure a LoRa antenna is attached to the Ikoka Stick before powering it on. Transmitting without an antenna can permanently damage the radio module.** - ---- - -## Parts List - -| # | Part | Qty | Price | Source | -|---|------|-----|-------|--------| -| 1 | GOME Ikoka Stick (HW v0.4.0) | 1 | $55 | [GitHub](https://github.com/ndoo/ikoka-stick-meshtastic-device) (group buy) | -| 2 | Waveshare Solar Power Manager (standard) | 1 | $15 | [Waveshare](https://www.waveshare.com/wiki/Solar_Power_Manager) | -| 3 | IP65 Junction Box, 220x170x110mm, Gray | 1 | $30 | [AliExpress](https://www.aliexpress.com/item/1005007587120013.html) | -| 4 | Solar panel, 10W/18V, 250x340mm | 1 | $35 | [Amazon](https://a.co/d/0eJo5GCr) | -| 5 | 3P1S 18650 battery pack (or 4P1S) | 1 | $25-35 | [MP&W Supply](https://mpandw.ca/) | -| 6 | Battery protection board (high/low voltage cutoff) | 1 | $8 | [Space Hedgehog](https://space-hedgehog.com/products/battery-protection-with-low-voltage-cut-off) | -| 7 | 890-960MHz 4-cavity bandpass filter, 50W (recommended) | 1 | $65 | [Alibaba](https://www.alibaba.com/product-detail/50W-890-960MHz-4-Cavity-Filter_1601399651944.html) | -| 8 | 10-15cm SMA right angle to N-Type female O-ring nut cable | 1 | $6 | [AliExpress](https://www.aliexpress.com/item/1005008569444661.html) | -| 9 | 7.5cm SMA Male 90° to SMA Male 90° cable | 1 | $7 | [AliExpress](https://www.aliexpress.com/item/1005006702037541.html) | -| 10 | 0.5ft USB-A to USB-C right angle cable | 1 | $7 | [Amazon](https://a.co/d/045htrEG) | -| 11 | M3x35 spacers | 4 | | | -| 12 | M3x5 screws | 8 | | | -| 13 | Gorilla Epoxy Syringe, 25ml | 1 | $15 | [Home Depot](https://www.homedepot.ca/product/gorilla-epoxy-syringe-25ml/1000778451) | -| 14 | Waterproof vent plug (breather valve) | 1 | $2 | [AliExpress](https://www.aliexpress.com/item/1005006370919409.html) | -| 15 | Outdoor silicone caulk (clear) | 1 | $10 | Any hardware store | -| 16 | Adafruit INA3221 (optional) | 1 | $15-20 | [DigiKey](https://www.digikey.ca/en/products/detail/adafruit-industries-llc/6062/25660599) | - -**Estimated total cost:** ~$280-290 without the INA3221, ~$300-310 with it. This does not include the antenna, 3D-printed parts, or mounting hardware (M3 spacers/screws). - -**Alternative filter option (minimal):** Callboost 915MHz cavity filter, 26M BW, $82. [AliExpress](https://www.aliexpress.com/item/1005004468960058.html) - -### Antenna - -An antenna is not included in this parts list as selection will vary by deployment. It is highly recommended to use a higher dBi antenna such as a 6-8 dBi or higher with 1W builds. We have found that the Ikoka Stick does not pair well with the Alfa 5.8 dBi antenna for some reason, with several folks in Ottawa observing noticeably worse signal quality when using them together. This is not a definitive statement, just a pattern observed across multiple deployments. - -For recommended antenna options, see the [GOME Repeater Omni Antennas](recommended-antenna.md#repeater-omni-antennas) page. - --- - -## Tools Required - -| Tool | Notes | -|------|-------| -| Soldering iron | Fine tip recommended for I2C wiring | -| Solder + flux | | -| Multimeter | For continuity and voltage checks | -| Step drill bit | For drilling clean holes in the junction box | -| Jigsaw or rotary tool | For cutting the solar panel opening | -| 1/8" drill bit | For opening up mounting holes | -| Pencil | For marking the cutout | -| Clamps + flat piece of wood | For clamping solar panel during epoxy cure | -| Wire strippers | | - +title: Review the 1 W solar repeater build +description: Review the experimental Ikoka Stick solar build, its evidence gaps, staged assembly, bench checks, and recovery plan. +audience: + - advanced-repeater-builder + - hardware-reviewer +task: review-1w-solar-repeater-build +scope: experimental +status: experimental +owner: docs-hardware +last_reviewed: 2026-07-19 +review_by: 2026-10-17 +difficulty: advanced +estimated_time: multiple days including fabrication and cure time +destructive: false +requires: + - multimeter + - soldering-experience + - fabrication-experience + - manufacturer-documentation +page_styles: + - assets/styles/devices-builds.css --- -## Prerequisites - -Before starting assembly, you will need the following 3D-printed parts. Both are available for download below: - -- **Mounting plate** for the junction box (holds the filter, Ikoka, and Waveshare board) -- **3P1S 18650 battery holder** (or sized to match your chosen battery configuration) - -### Downloads - -- [Mounting plate + 3P1S 18650 battery holder (combined .3mf)](./files/repeater-solar-1w-diy-build-plate-and-battery-holder.3mf) -- [Mounting plate (.stl)](./files/repeater-solar-1w-diy-build-plate.stl) -- [3P1S 18650 battery holder (.stl)](./files/repeater-solar-1w-diy-build-3x18650-battery-holder.stl) - ---- - -## Assembly Steps - -### Enclosure Preparation - -1. Flip the solar panel face-down. On the back, locate the black junction box where the red and black power wires exit. Measure this junction box with calipers or a ruler. - -2. On the **front face** of the gray junction box, use a pencil to trace a rectangle matching the solar panel junction box dimensions. Center it near the top of the panel. - -3. Using a step drill bit, drill a large starter hole in the center of the traced rectangle. - -4. Using a jigsaw or rotary tool, cut along the traced lines to remove the rectangular section. Clean up any rough edges. - -### Mounting the Solar Panel - -5. Mix a batch of Gorilla Epoxy and apply it generously across the entire front face of the junction box, covering the area where the solar panel will sit. - -6. Route the solar panel's power cable through the rectangular cutout from the outside into the box. - - ![Solar panel wires routed through junction box](./images/repeater-solar-1w-diy-build-1.jpg){ width="300" } - -7. Press the solar panel onto the epoxied face of the junction box. Place a flat piece of wood across the front of the panel and use clamps on all four corners to apply even pressure. Be careful not to over-tighten, as this can crack the panel. Alternatively, lay the assembly flat on the ground with weights on the back of the box. - -8. Allow the epoxy to fully cure according to the manufacturer's instructions before removing the clamps. - -9. Using clear outdoor silicone caulk, apply a bead around all four edges where the solar panel meets the junction box. Smooth the caulk to create a weatherproof seal. - -### Mounting Plate and Filter - -10. Attach the four M3x35 spacers to the corners of the junction box mounting plate. You may need to use a 1/8" drill bit to open the holes slightly to fit M3 screws through the plate. Use the 3D-printed mount plate as a reference for hole placement. - - ![Mounting plate with spacers and filter assembled](./images/repeater-solar-1w-diy-build-2.jpg){ width="300" } - -11. Mount the RF bandpass filter to the mounting plate, positioning it roughly in the center. - -12. Connect the N-Type to SMA cable to the filter's **output** port. This cable will pass through the box wall to the external antenna. +# Review the 1 W Solar Repeater Build -13. Determine where the N-Type connector should exit the junction box. Using a step drill bit, drill the hole, stepping up one size at a time and test-fitting after each step until the connector fits snugly. +This page preserves an Ottawa experimental build around the GOME Ikoka Stick hardware revision 0.4.0, a Waveshare Solar Power Manager, a filtered RF path, and a fabricated enclosure. -14. Feed the N-Type connector through the box wall from the inside out and tighten the O-ring nut to secure it. +
-15. On the **bottom** of the junction box, drill a hole for the waterproof vent plug using the step drill bit. - -16. Install the vent plug and tighten it. - - ![Filter mounted in box with N-Type, SMA cables, and vent plug installed](./images/repeater-solar-1w-diy-build-3.jpg){ width="300" } - -### Filter and Radio Preparation - -17. Connect the SMA-to-SMA cable to the filter's **input** port. Finger-tighten for now. - -18. **Waveshare Solar Power Manager prep (standard model):** On the back of the board, solder a wire across the boot button pads to short it permanently. Without this modification, the board will not automatically resume power output after a power loss, meaning your repeater would stay offline until you physically press the button. - - ![Boot button shorted on Waveshare Solar Power Manager](./images/repeater-solar-1w-diy-build-4.jpg){ width="300" } - -### Component Mounting - -19. Outside the box (for easier access), mount the Ikoka Stick and the Waveshare Solar Power Manager to the 3D-printed mounting plate. If using the optional INA3221, mount it as well. - -20. **(Optional, Adafruit INA3221)** Using a soldering iron, carefully remove the Ikoka Stick's OLED display header pins. Work slowly and gently to avoid damaging nearby components. - -21. **(Optional, Adafruit INA3221)** Solder the INA3221 to the Ikoka's I2C bus using the display header pads. See the [INA3221 Wiring](#ina3221-wiring-optional) section below for the pin connections. - - ![INA3221 wired to Ikoka Stick I2C with Waveshare Solar Power Manager](./images/repeater-solar-1w-diy-build-5.jpg){ width="300" } - -22. Install the mounting plate assembly into the junction box and secure it with M3x5 screws into the M3x35 spacers. - -### Battery Installation - -23. Attach the 3D-printed battery holder to the inside front of the case, below where the solar panel wires enter. Use double-sided tape or epoxy to secure it. - -24. If your 18650 battery pack does not have a built-in PCM (protection circuit module), wire the battery protection board between the battery and the Waveshare. Follow the polarity markings carefully. - -### Power Wiring - -25. **(Optional, Adafruit INA3221) Solar wiring:** Connect the solar panel positive wire to INA3221 **CH3+**. Connect CH3- to the Waveshare solar input positive. Connect the solar panel negative wire directly to the Waveshare solar input negative. - -26. **(Optional, Adafruit INA3221) Battery wiring:** Connect the battery (or the PCM) positive wire to INA3221 **CH1+**. Connect CH1- to the positive pin on the Waveshare battery JST PH2.0 connector. Connect the battery negative wire directly to the negative pin on the JST PH2.0 connector. Plug the connector into the Waveshare board. - - ```Note: If you are not using the INA3221, wire the solar panel and battery directly to the Waveshare board's solar and battery inputs.``` - -### Final Connections - -27. Connect the SMA cable from the filter's input to the Ikoka Stick's SMA connector. Tighten both ends firmly with a wrench or pliers. A loose RF connection will cause signal loss. - -28. Run the USB-A to USB-C cable from the Waveshare Solar Power Manager's USB output to the Ikoka Stick's USB-C input. - - *If using the Adafruit INA3221, your completed assembly should look similar to this:* - - ![Completed build with INA3221, Waveshare, and Ikoka Stick](./images/repeater-solar-1w-diy-build-6.jpg){ width="300" } - - *Without the INA3221:* - - ![Completed build without INA3221](./images/repeater-solar-1w-diy-build-7.jpg){ width="300" } - - *Full interior view with INA3221, battery pack, and all wiring complete:* - - ![Full interior view of completed build with INA3221](./images/repeater-solar-1w-diy-build-8.jpg){ width="300" } - ---- - -## INA3221 Wiring (Optional) - -The Adafruit INA3221 connects to the Ikoka Stick via I2C using the **OLED display header** pads. Remove the display header pins and solder wires directly to the pads. - -**I2C connection (Ikoka display header to INA3221):** - -| Ikoka Display Header | INA3221 Pin | Wire | -|----------------------|-------------|------| -| Pin 1 - GND | GND | Ground | -| Pin 2 - VCC (3.3V) | VCC | Power | -| Pin 3 - SCL | SCL | Clock | -| Pin 4 - SDA | SDA | Data | - -**INA3221 channel assignments:** - -| Channel | Connected To | Purpose | -|---------|--------------|---------| -| CH1 | Battery | Monitor battery voltage and current draw | -| CH2 | (unused) | Available for future use | -| CH3 | Solar | Monitor solar input voltage and charging current | - -```Note: VCC on the INA3221 is the board power pin (3.3V from the Ikoka). VIN1/VIN2/VIN3 are the measurement channel inputs, not the power pin.``` - -**Firmware build flags:** MeshCore does not auto-detect the Adafruit INA3221 at its default address. Add these flags to your build configuration: - -```ini -[env:ikoka_stick_nrf_30dbm_repeater] -build_flags = - ${ikoka_stick_nrf_repeater.build_flags} - ${ikoka_stick_nrf_e22_30dbm.build_flags} - -D TELEM_INA3221_ADDRESS=0x40 - -UENV_INCLUDE_INA219 -``` - -The Adafruit INA3221 sits at I2C address `0x40`, but MeshCore expects `0x42`. The `-D TELEM_INA3221_ADDRESS=0x40` flag corrects this. The `-UENV_INCLUDE_INA219` flag disables INA219 support to prevent conflicts. - ---- +**Experimental — do not build or deploy without domain review.** The content migration did not reproduce the hardware, verify the electrical design, pin the firmware target, validate optional telemetry, recheck prices, or establish expected measurements and climate limits. -## Wiring Diagram +
-![Wiring diagram](./images/repeater-solar-1w-diy-build-9.svg){ width="600" } +
+
Original author
MrAlders0n, Ottawa
+
Source date
January 1, 2026
+
Hardware note
GOME Ikoka Stick HW 0.4.0
+
Power note
Waveshare Solar Power Manager, standard model
+
Firmware target
Not pinned or reproduced here
+
Maintenance interval
Must be set by the site review
+
+ +## Is this build appropriate? + +The original field note positioned this as a specialized backbone or long-reach experiment. A 1 W radio is not a default upgrade. Coordinate with adjacent communities and use evidence from the intended site before changing network coverage or traffic patterns. + +Use the [300 mW draft build](repeater-solar-300mw-diy-build.md) or a reviewed pre-built path unless a network operator and hardware/RF reviewer agree that this experiment addresses a measured need. + +## Before you start + +!!! danger "This combines high-power RF, lithium batteries, charging, soldering, cutting, and an outdoor elevated installation" + Verify every component and limit from current manufacturer documentation. Keep an antenna and reviewed RF path attached before any transmit test. Do not energize a battery or charger path when polarity, protection, wiring, temperature, or expected readings are unknown. + +
    +
  • The local network need and cross-region effects are documented.
  • +
  • The exact Ikoka hardware and repeater firmware target are confirmed and recoverable by USB.
  • +
  • A hardware/electrical reviewer approves the battery, charger, panel, wiring, protection, optional telemetry, and enclosure thermal plan.
  • +
  • An RF reviewer approves the radio, filter, feed line, antenna, connectors, and site.
  • +
  • A structural/site review covers the finished mass, panel area, mount, wind, ice, snow, cable route, and access.
  • +
  • The build will remain supervised on the bench until a commissioning record is approved.
  • +
+ +## What this build changes + +The procedure cuts and drills an enclosure, bonds a solar panel with epoxy and sealant, modifies the mounting system, connects a high-power RF path and filter, assembles a lithium power system, and may remove display pins for optional telemetry. Several changes are difficult or impossible to undo without replacing parts. + +## Legacy bill of materials + +
+ +| # | Legacy product lead | Qty | Legacy CAD figure | Detail to verify | Source | +|---:|---|---:|---:|---|---| +| 1 | GOME Ikoka Stick HW 0.4.0 | 1 | $55 | Hardware revision, Canadian band, RF output, current firmware target, recovery | [Project page](https://github.com/ndoo/ikoka-stick-meshtastic-device) | +| 2 | Waveshare Solar Power Manager, standard model | 1 | $15 | Exact model, panel/battery/output limits, startup behaviour, temperature | [Waveshare](https://www.waveshare.com/wiki/Solar_Power_Manager) | +| 3 | IP65 junction box, 220×170×110 mm | 1 | $30 | Material, actual ingress rating after modification, temperature, vent | [AliExpress](https://www.aliexpress.com/item/1005007587120013.html) | +| 4 | 10 W / 18 V panel, 250×340 mm | 1 | $35 | Electrical limits, dimensions, mass, weather/load suitability | [Amazon lead](https://a.co/d/0eJo5GCr) | +| 5 | 3P1S or 4P1S 18650 pack | 1 | $25–35 | Chemistry, matched cells, protection, construction, charger and temperature | [MP&W Supply](https://mpandw.ca/) | +| 6 | Battery protection board | 1 | $8 | Exact thresholds, current, temperature, wiring, compatibility | [Space Hedgehog](https://space-hedgehog.com/products/battery-protection-with-low-voltage-cut-off) | +| 7 | 890–960 MHz four-cavity band-pass filter, 50 W | 1 | $65 | Frequency response at 902–928 MHz, insertion loss, connectors, weather/temperature | [Alibaba lead](https://www.alibaba.com/product-detail/50W-890-960MHz-4-Cavity-Filter_1601399651944.html) | +| 8 | 10–15 cm SMA right-angle to N-type female bulkhead cable | 1 | $6 | Connector gender/polarity, loss, bend, seal | [AliExpress](https://www.aliexpress.com/item/1005008569444661.html) | +| 9 | 7.5 cm SMA male right-angle cable | 1 | $7 | Connector match, loss, bend, power handling | [AliExpress](https://www.aliexpress.com/item/1005006702037541.html) | +| 10 | 0.5 ft USB-A to USB-C right-angle cable | 1 | $7 | Data/power needs, current, fit, strain | [Amazon lead](https://a.co/d/045htrEG) | +| 11–12 | M3×35 spacers and M3×5 screws | as needed | Not recorded | Material, strength, fit, corrosion | Local source | +| 13 | Gorilla Epoxy, 25 ml | 1 | $15 | Material compatibility, cure, outdoor/temperature limits | [Home Depot](https://www.homedepot.ca/product/gorilla-epoxy-syringe-25ml/1000778451) | +| 14 | Waterproof vent plug | 1 | $2 | Thread, airflow, ingress performance after install | [AliExpress](https://www.aliexpress.com/item/1005006370919409.html) | +| 15 | Clear outdoor silicone sealant | 1 | $10 | Material compatibility, cure, UV/temperature limits | Local hardware source | +| 16 | Adafruit INA3221, optional | 1 | $15–20 | Electrical interface, address, firmware support, isolation, calibration | [DigiKey](https://www.digikey.ca/en/products/detail/adafruit-industries-llc/6062/25660599) | + +
+ +The previous guide also listed a Callboost 915 MHz cavity filter as a legacy alternative lead: [AliExpress](https://www.aliexpress.com/item/1005004468960058.html). No comparative filter evidence is attached here. + +

The original guide estimated roughly $280–310 CAD before antenna, printed parts, and some mounting hardware. The source was dated January 1, 2026; prices and bundle contents were not rechecked.

+ +## Tools and downloads + +The legacy build used soldering tools, a multimeter, wire tools, step and small drill bits, a jigsaw or rotary tool, marking tools, clamps, and suitable personal protection. Select tools and controls appropriate to the reviewed materials and work. + +Legacy printable files: + +- [Mounting plate and battery holder (.3mf)](files/repeater-solar-1w-diy-build-plate-and-battery-holder.3mf) +- [Mounting plate (.stl)](files/repeater-solar-1w-diy-build-plate.stl) +- [3P1S 18650 battery holder (.stl)](files/repeater-solar-1w-diy-build-3x18650-battery-holder.stl) + +Confirm dimensions, material, temperature performance, fasteners, battery restraint, and print quality before use. + +## Assembly stages + +
+
+

Stage 1 — Review and dry-fit

+

Confirm all revisions, dimensions, electrical limits, connector paths, filter orientation, clearances, printed parts, cable bends, vent location, panel location, and service access before cutting or bonding.

+

Checkpoint: the reviewed schematic, RF path, and mechanical layout agree with the exact parts in hand.

+
+
+

Stage 2 — Prepare the enclosure and panel

+
    +
  1. Measure the panel junction box and mark the enclosure cutout from the actual parts.
  2. +
  3. Use an appropriate pilot opening and cutting tool with the enclosure empty and safely supported.
  4. +
  5. Deburr and clean the cutout, then dry-fit the panel and cable route.
  6. +
  7. Bond and clamp only with a reviewed material-compatible process. Let it cure for the full manufacturer time.
  8. +
  9. Apply the reviewed weather seal without blocking drainage or venting.
  10. +
+
Legacy example of solar-panel wires routed through the enclosure cutout
Legacy fabrication example; measurements must come from the actual parts.
+
+
+

Stage 3 — Install the mounting plate, filter, bulkhead, and vent

+
    +
  1. Fit the reviewed spacers and mounting plate without stressing the enclosure.
  2. +
  3. Mount the filter with identified input and output ports and adequate cable bend radius.
  4. +
  5. Drill and deburr the bulkhead and vent holes by test-fitting the actual hardware.
  6. +
  7. Install seals, bulkhead, and vent according to their documentation.
  8. +
  9. Leave RF connections unpowered and accessible for inspection.
  10. +
+
Legacy mounting plate with spacers and RF filter
Legacy plate and filter layout.
+
Legacy enclosure with filter, RF bulkhead cables, and vent plug
Legacy RF and vent layout; connector direction and seals require review.
+
+
+

Stage 4 — Review the power-manager startup modification

+

The legacy guide described shorting the standard Waveshare manager's boot-button pads so output would resume after a power loss. That modification has not been reproduced or reviewed here.

+

Do not apply it until the exact model schematic, failure modes, warranty, safe soldering method, startup test, and rollback are approved by a hardware reviewer.

+
Legacy photograph of a wire across solar-manager boot-button pads
Historical modification photograph, not an instruction to solder the current hardware.
+
+
+

Stage 5 — Mount the radio and decide on optional telemetry

+

Dry-fit the Ikoka Stick and power manager. The optional INA3221 branch removes the display header and solders to I2C pads, so it must remain separate from the base build.

+

The legacy notes recorded display-header pins as GND, 3.3 V, SCL, and SDA and assigned battery to channel 1 and solar to channel 3. Those pin assignments, measurement topology, calibration, current range, I2C address, and firmware support have not been independently verified.

+

Default: omit optional telemetry. Do not remove display pins or apply historical firmware flags until a reviewer reproduces the exact hardware and current MeshCore target.

+
Legacy optional INA3221 connection to the Ikoka display-header area
Historical optional branch, quarantined pending electrical and firmware review.
+
+
+

Stage 6 — Assemble the battery and charging path

+
    +
  1. Keep solar, battery, USB, and radio disconnected.
  2. +
  3. Confirm the battery pack construction, cell matching, protection, restraint, insulation, and temperature limits.
  4. +
  5. Confirm every power-manager and protection-board terminal from current manufacturer documentation.
  6. +
  7. Measure and record polarity; do not rely on wire colour.
  8. +
  9. Connect one reviewed branch at a time and inspect before proceeding.
  10. +
+

Checkpoint: no unknown terminal, limit, expected value, exposed conductor, or unsupported battery condition remains.

+
+
+

Stage 7 — Complete the RF and USB paths

+
    +
  1. Verify filter input/output direction and every SMA/N-type connector.
  2. +
  3. Attach the complete reviewed antenna path before the radio can transmit.
  4. +
  5. Route the USB power cable without sharp bends, abrasion, or strain.
  6. +
  7. Keep the enclosure open for supervised first power and commissioning.
  8. +
+
Legacy completed interior with optional INA3221 telemetry
Legacy optional-telemetry layout.
+
Legacy completed interior without optional INA3221 telemetry
Legacy base-layout example.
+
Legacy full enclosure interior with radio, filter, power manager, and battery
Legacy interior overview; do not infer electrical approval from the photograph.
+
+
+ +## Wiring diagram status + +![Legacy 1 W solar repeater wiring diagram](images/repeater-solar-1w-diy-build-9.svg){ .mc-build-photo loading=lazy } + +The diagram is retained as a historical review artifact. It is not an approved schematic. A reviewer must reconcile it with the exact product documentation, protection requirements, conductor ratings, grounds, optional telemetry, and measured results. + +## Expected readings and stop conditions + +No numeric acceptance range has been peer-reviewed for this page. Do not substitute guessed voltages or currents. + +
+ +| Check | Acceptable result | Stop condition | +|---|---|---| +| Disconnected continuity | No unintended short on the documented supply or RF paths | Unexpected continuity or uncertain meter setup | +| Connector polarity | Measurements match the reviewed schematic and manufacturer pinout | Any disagreement or unknown pin | +| Solar, battery, and output values | Each measured value is within every connected component's current documentation | Missing limit or out-of-range value | +| First power | Stable expected startup with no heat, odour, swelling, smoke, noise, or cycling | Any abnormal physical or electrical behaviour | +| RF path | Correct band, filter direction, connections, and attached antenna before transmit | Missing antenna, loose/forced connector, unknown filter response | +| Optional telemetry | Independently reproduced measurement and firmware behaviour | Unverified wiring, address, calibration, or build target | + +
+ +## Bench test and commissioning + +
    +
  • Record exact parts, revisions, source documents, schematic, photographs, polarity, and measured values.
  • +
  • Use supervised and current-limited first-power methods appropriate to the reviewed hardware.
  • +
  • Confirm the unit starts after each intended power source is applied and after a controlled power interruption.
  • +
  • Confirm the repeater's identity, firmware, radio, region, path, advert, and access settings survive reboot.
  • +
  • Confirm a nearby companion receives an advert and the planned local routing test succeeds.
  • +
  • Measure the RF/power behaviour using the reviewer's approved method; do not infer output from a firmware setting alone.
  • +
  • Run a supervised charge/load test that covers the reviewed conditions without exceeding component limits.
  • +
  • Inspect cable strain and seals, then complete an enclosure-appropriate water-resistance test.
  • +
  • Prove USB recovery and document the removal/access plan.
  • +
+ +Do not deploy until a hardware/electrical reviewer, RF reviewer, and site owner approve the commissioning record. + +## Recovery and undo + +If a check fails, stop transmitting and disconnect solar, USB, and battery power using the reviewed safe sequence. Move the equipment to a suitable supervised work area. Do not reconnect a warm, swollen, damaged, leaking, or otherwise questionable lithium battery. Restore firmware by USB only after the electrical/RF fault is understood. + +Cutouts, epoxy, removed pins, and solder bridges may be irreversible. Replace uncertain components rather than improvising a repair. The safe rollback from the optional telemetry branch is to omit it before modification, not to assume removed hardware can be restored. + +## Maintenance + +No interval is approved. The site record must define inspections for mounting, panel/bond, water entry, vent, seals, corrosion, cables/connectors, filter, battery condition, charge behaviour, measured values, firmware/configuration, radio verification, and physical recovery. Reinspect after any severe event or unexplained behaviour defined by the site reviewer. + +## Sources and change log + +- Original community guide: MrAlders0n (Ottawa), January 1, 2026. +- July 19, 2026: converted to an experimental review guide; separated optional telemetry, removed executable unverified firmware flags, added evidence limits, staged checkpoints, measurements, commissioning, recovery, and maintenance requirements. No hardware or firmware claim was upgraded to verified. + +## Next step + +Review the [repeater selection guide](recommended-repeaters.md) with the local network operator. If the experiment is approved and commissioned, complete the [mounting plan](repeater-mounting-options.md) and [repeater configuration](../config/index.md). diff --git a/docs/hardware/repeater-solar-300mw-diy-build.md b/docs/hardware/repeater-solar-300mw-diy-build.md index c0447bc..329e999 100644 --- a/docs/hardware/repeater-solar-300mw-diy-build.md +++ b/docs/hardware/repeater-solar-300mw-diy-build.md @@ -1,102 +1,216 @@ -# Building a Solar Node – Rak Unify Box - -Authored By: MrAlders0n (Ottawa) -Date: 2026-01-01 - -!!! warning "No warranty" - This is to help the community understand how to make a repeater, and I by no means provide any warranty for anyone following this guide. Please test everything first with a multimeter and other tools before powering anything on. - -This guide walks through assembling a solar-powered MeshCore repeater using the **RAK Unify Box** enclosure. -Follow each step carefully for a reliable and weatherproof build. - -## Parts List - -| Item | Product Name | Cost (CAD) | Link | -|----------------------|---------------------------------------|------------|------| -| **Enclosure** | WisMesh Unify Enclosure 910422 | $72.50 | [AliExpress](https://aliexpress.com/item/1005008369061766.html) | -| **LoRa Board (Small)** | RAK WisBlock RAK19003/RAK4631 (Type 6) | $36.38 | [AliExpress](https://aliexpress.com/item/1005008285698839.html) | -| **Antenna** | ALFA AOA-915-5ACM | $34.99 | [Amazon](https://www.amazon.ca/dp/B08H8J6ZV6) | -| **Antenna Coax Cable** | N Female to IPX | $6.79 | [AliExpress](https://aliexpress.com/item/1005001920963497.html) | -| **Battery - Option 1** | 3000mAh Li-ion - Makerfocus (Pack of 4) - Ships from China | $34.10 | [Makerfocus](https://www.makerfocus.com/products/makerfocus-3-7v-3000mah-lithium-rechargeable-battery-1s-3c-lipo-battery-pack-of-4) | -| **Battery - Option 2** | 3000mAh Li-ion - Amazon US store (Pack of 4) | $40.92 | [Amazon](https://www.amazon.com/3000mAh-Rechargable-Protection-Insulated-Development/dp/B08T6GT7DV) | -| **Battery - Option 3** | 3000mAh Li-ion - Sold at Local store (Space Hedgehog) | $11.00 | [Space Hedgehog](https://space-hedgehog.com/products/3000mah-battery) | -| **Battery Protection ^** | Space Hedgehog (Local Store) Li-ion PCM | $6.00 | [Space Hedgehog](https://space-hedgehog.com/products/battery-protection-with-low-voltage-cut-off?variant=51646910660664) | -| **Vent** | Waterproof Vent Plug (M12X1.5-10) | $6.12 | [AliExpress](https://aliexpress.com/item/1005006370919409.html) | - -^ If you're using the Makerfocus flat battery: This already includes a PCM, the extra PCM is an added safety measure. If you're using unprotected batteries (e.g., 18650 button top), then you will need to purchase a PCM. - -**Approximate total cost: $180 CAD** - +--- +title: Build a 300 mW solar repeater +description: Review the staged RAK Unify community build with explicit hazards, verification gaps, bench checks, and maintenance records. +audience: + - repeater-builder + - hardware-reviewer +task: build-300mw-solar-repeater +scope: ottawa-field-practice +status: draft +owner: docs-hardware +last_reviewed: 2026-07-19 +review_by: 2026-10-17 +difficulty: intermediate +estimated_time: one day plus sealant cure time +destructive: false +requires: + - multimeter + - safe-work-area + - manufacturer-documentation +page_styles: + - assets/styles/devices-builds.css --- -## Assembly Steps - -**WARNING: Always ensure a LoRa antenna and the Bluetooth antenna are attached to the RAK board before powering it on. Powering without antennas can permanently damage the board.** - -1. Unbox all components and place them aside. - ```Tip: Try not to misplace the small screws and fittings — they’re easy to lose.``` - -2. Mount the RAK backplate into the box with the four provided screws. - -3. Drill two holes: one for the N-type antenna mount and one for the drain plug. - * Use a step drill bit for clean holes. - * **Finding the top of the box:** - * Flip the box onto its back. - * Locate the mount hole marked "1" — this is the top. - * Drill slowly, one step at a time. Test-fit the N-type connector after each step until it fits snugly. - * Repeat the same process on the bottom of the box for the drain plug. - -4. Attach both the N-type antenna mount and the drain plug. - -5. Connect the N-type antenna to the LoRa IPEX connector on the RAK19003 board. - -6. Mount the Bluetooth antenna to the side of the box using the included double-sided tape. - -7. Connect the Bluetooth IPEX to the RAK19003 board. - -8. Connect an antenna to the N-type connector, then flash and configure the RAK unit following Configuring a Repeater. - -9. Mount the RAK unit onto the backplate, see picture below for what it should look like at this step. - - ![](images/BuildRepeater1_MountedAll.jpeg){ width="300" } - -10. Connect the JST PHR-2 cable to the RAK19003 battery plug, **ensuring correct polarity** (many JST cables are wired incorrectly). - - ![](images/RAK19003-Layout.png){ width="300" } - -11. Connect the other end of this cable to the **CHG** side of the Li-ion PCM. The following picture of the PCM is from VoltaicEnclosures which is what we used initially, but it is the same principle for our current recommended PCM: - - ![](images/VoltaicEnclosures_Layout.png){ width="300" } - -12. Slide a piece of heat-shrink tubing over the cable large enough to cover the PCM before connecting the battery. - -13. Connect the LiPo JST PHR-2 cable to the **BATT** side of the PCM, again **ensuring polarity is correct**. - - ![](images/RAK19003-LayoutSolar.jpg){ width="300" } - -14. Heat-shrink the Li-ion PCM so the entire board is covered. - -15. (Optional) Secure the PCM to the backplate using double-sided tape. It can also float freely inside the box if preferred. - -16. (Optional) Secure the battery to the backplate with double-sided tape or mounting hardware. It should look like the below image. - - ![](images/BuildRepeater1_Finished.jpeg){ width="300" } - -17. Fit the rubber seal into the groove around the edge of the front plate of the box. - -18. Connect the solar panel wire from the front plate to the RAK19003. - - ```Be careful not to let the seal fall out of place while connecting the solar panel wire.``` - -19. Join the front and back plates together and fasten them with the six screws. - - ```Tighten securely to maintain the weatherproof seal.``` - -20. Wrap the entire N-type connector and the exposed metal part of the Alfa antenna with self-adhering silicone tape, or use two layers of heat-shrink tubing for protection. - -21. Apply a bead of clear outdoor silicone caulk around the base of the N-type connector to prevent water from leaking into the box. - - ![](images/BuildRepeater1_SelfFuseTape.jpeg){ width="300" } - ![](images/BuildRepeater1_Heatshrink.jpg){ width="300" } - -22. (Optional) Add a bead of silicone caulk along the top edge of the box seal (between the two plates) and around the base of the antenna as extra waterproofing protection. +# Build a 300 mW Solar Repeater + +This staged version preserves MrAlders0n's Ottawa RAK Unify community build notes, originally dated January 1, 2026. + +
+ +**Draft — factual and electrical review required.** The content migration did not independently verify the bill of materials, prices, battery/charger limits, connector polarity, weather rating, firmware target, expected measurements, or deployment lifetime. Do not energize or deploy the build until those details are checked for the exact revisions in hand. + +
+ +
+
Original author
MrAlders0n, Ottawa
+
Source date
January 1, 2026
+
Hardware note
RAK19003 / RAK4631 Type 6
+
Firmware target
Current supported repeater target; not pinned here
+
Weather target
Not independently verified
+
Maintenance interval
Must be set for the site
+
+ +## Before you start + +This build cuts an enclosure, assembles an RF path, and connects a lithium battery to a solar charging system. It also creates equipment intended for an exposed, elevated installation. + +!!! danger "Stop until the complete system is reviewed" + Verify the exact battery chemistry, protection, charger limits, connector polarity, wire ratings, solar input, enclosure, vent, antenna path, and weatherproofing in manufacturer documentation. Use a qualified person for electrical or structural details outside your competence. + +
    +
  • Every part and hardware revision matches a dated source.
  • +
  • The radio board appears in the current official MeshCore flasher for the repeater role.
  • +
  • The antenna is for the Canadian 902–928 MHz band and its complete connector path is confirmed.
  • +
  • The battery and solar power path have documented protection and compatible limits.
  • +
  • You have a multimeter, eye protection, suitable drilling/cutting tools, and a safe work area.
  • +
  • You have a USB recovery cable and will keep the unit on the bench until commissioning passes.
  • +
+ +## What this build changes + +The procedure drills enclosure openings, installs an external antenna connector and vent, connects the radio and antenna, adds battery protection in the power path, and seals the enclosure. These changes can be difficult to undo without replacing parts. + +## Bill of materials + +
+ +| System part | Legacy product lead | Quantity | Legacy CAD figure | What to verify | Source | +|---|---|---:|---:|---|---| +| Enclosure | WisMesh Unify Enclosure 910422 | 1 | $72.50 | Exact revision, panel/charger limits, seals, venting, included hardware | [AliExpress](https://aliexpress.com/item/1005008369061766.html) | +| LoRa board | RAK WisBlock RAK19003 / RAK4631, Type 6 | 1 | $36.38 | Exact board, Canadian band, firmware target, connectors | [AliExpress](https://aliexpress.com/item/1005008285698839.html) | +| Antenna | Alfa AOA-915-5ACM | 1 | $34.99 | Band, connector, mount, weather exposure | [Amazon Canada](https://www.amazon.ca/dp/B08H8J6ZV6) | +| Antenna pigtail | N female to IPEX | 1 | $6.79 | IPEX generation, connector gender/polarity, length, loss | [AliExpress](https://aliexpress.com/item/1005001920963497.html) | +| Battery option | MakerFocus 3000 mAh Li-ion pack | 1 | $34.10 per legacy pack | Chemistry, dimensions, connector, polarity, protection, charger limits | [MakerFocus](https://www.makerfocus.com/products/makerfocus-3-7v-3000mah-lithium-rechargeable-battery-1s-3c-lipo-battery-pack-of-4) | +| Battery option | Legacy Amazon US 3000 mAh pack | 1 | $40.92 per legacy pack | Same checks; shipping/import restrictions | [Amazon US](https://www.amazon.com/3000mAh-Rechargable-Protection-Insulated-Development/dp/B08T6GT7DV) | +| Battery option | Space Hedgehog 3000 mAh battery | 1 | $11 | Same checks; current stock and revision | [Space Hedgehog](https://space-hedgehog.com/products/3000mah-battery) | +| Additional protection lead | Li-ion PCM | 1 | $6 | Whether required/compatible, current/voltage/temperature limits, wiring | [Space Hedgehog](https://space-hedgehog.com/products/battery-protection-with-low-voltage-cut-off?variant=51646910660664) | +| Vent | M12×1.5 waterproof vent plug | 1 | $6.12 | Thread, sealing method, airflow, enclosure compatibility | [AliExpress](https://aliexpress.com/item/1005006370919409.html) | + +
+ +

The original guide estimated about $180 CAD. That source was dated January 1, 2026; the prices and bundle contents were not rechecked during this migration and exclude some mounting/shipping costs.

+ +## Tools and consumables + +- multimeter and manufacturer documentation for each electrical component; +- eye protection and suitable drilling/cutting tools; +- step drill bit sized by test-fitting the actual bulkhead and vent; +- wire tools and insulation/heat-shrink appropriate to the selected parts; +- self-fusing silicone tape or another reviewed outdoor connector-sealing method; +- outdoor sealant compatible with the enclosure materials; and +- known-good data USB cable for flashing and recovery. + +## Assembly stages + +
+
+

Stage 1 — Inspect before modifying

+

Confirm every part number and revision. Inspect the enclosure seals and hardware. Identify the enclosure top, antenna location, vent location, cable clearances, and service access before drilling.

+

Checkpoint: stop if any connector, polarity, protection, charger limit, or enclosure detail is uncertain.

+
+
+

Stage 2 — Prepare the enclosure and RF path

+
    +
  1. Mount the RAK backplate with the supplied hardware.
  2. +
  3. Mark separate openings for the N-type bulkhead and vent. Drill slowly with suitable protection, deburr the holes, and test-fit after each step.
  4. +
  5. Install the bulkhead and vent using the reviewed seals and orientation.
  6. +
  7. With all power disconnected, attach the radio-side pigtail to the correct LoRa connector.
  8. +
  9. Install the Bluetooth antenna described by the exact board documentation.
  10. +
  11. Attach the external LoRa antenna before any power or transmit test.
  12. +
+

Checkpoint: the RF path is mechanically secure, unforced, correctly mated, and attached before power.

+
+
+

Stage 3 — Flash and dry-fit

+
    +
  1. Keep the unit open and accessible.
  2. +
  3. Follow the safe repeater flashing guide for the exact board.
  4. +
  5. Mount the radio on the backplate and confirm that cables are not pinched or sharply bent.
  6. +
+
+ RAK board and antenna cables dry-fitted on the enclosure backplate +
Legacy community dry-fit example; verify the exact board and cable routing in hand.
+
+
+
+

Stage 4 — Verify and connect the power path

+
    +
  1. With power disconnected, identify the battery, charger, and any protection-board terminals from their current manufacturer documentation.
  2. +
  3. Verify both JST cable polarities with a multimeter; do not rely on wire colour alone.
  4. +
  5. Connect the charger side and battery side only according to the reviewed diagram for the exact protection device.
  6. +
  7. Insulate and mechanically secure the protection board and battery without crushing, heating, puncturing, or trapping them against sharp edges.
  8. +
+
+ Legacy RAK19003 connector layout used as a visual orientation note +
Legacy orientation image; the current hardware documentation is authoritative.
+
+
+ Legacy protection-board connection example from an earlier enclosure build +
Conceptual legacy example only; do not infer terminal labels or limits for another protection board.
+
+
+ Legacy solar enclosure power-layout example showing battery and protection wiring +
Confirm polarity and every electrical limit before reproducing the path.
+
+
+
+

Stage 5 — Seal only after bench verification

+
    +
  1. Fit the enclosure gasket without twisting or pinching it.
  2. +
  3. Connect the enclosure solar lead only after its polarity and limits are confirmed.
  4. +
  5. Close the enclosure evenly with the specified hardware and torque/process from its documentation.
  6. +
  7. Apply the reviewed outdoor weather-sealing method to the exposed antenna connection without blocking drainage or venting.
  8. +
+
+ Legacy completed RAK Unify repeater interior before the enclosure is closed +
Legacy completed interior example; part revisions and routing may differ.
+
+
+ Legacy example of self-fusing tape around an outdoor antenna connector +
One community weather-sealing example, not a universal installation method.
+
+
+ Legacy example of heat-shrink used around an outdoor antenna connection +
Confirm that the chosen method is compatible, inspectable, and does not trap water.
+
+
+
+ +## Expected readings and stop conditions + +No numeric electrical acceptance range has been peer-reviewed for this page. Use the current manufacturer limits for the exact battery, charger, protection device, radio board, and panel. + +
+ +| Check | Acceptable result | Stop condition | +|---|---|---| +| Power disconnected continuity | No unintended short across the documented supply path | Unexpected continuity, unstable reading, or uncertain meter setup | +| Polarity | Every connector matches its documented polarity before mating | Wire colour and measured/documented polarity disagree | +| Battery and solar input | Measured values remain within every component's published limits | Any value is outside a limit or the limit is unknown | +| First power | No heat, odour, swelling, smoke, noise, or unstable restart | Any abnormal physical or electrical behaviour | +| Radio current/operation | Behaviour is consistent with the reviewed board and role documentation | Current draw or behaviour cannot be explained from evidence | + +
+ +## Bench test and commissioning + +
    +
  • Photograph and record the exact parts, revisions, wiring, polarity checks, and measured values.
  • +
  • Power from a supervised, current-limited bench source where appropriate for the reviewed hardware.
  • +
  • Confirm the repeater boots, reconnects, and retains firmware, identity, radio, region, advert, and access settings after reboot.
  • +
  • Confirm a nearby companion receives an advert and the planned local routing test succeeds.
  • +
  • Exercise the intended solar/charge path under supervised conditions supported by the component documentation.
  • +
  • Inspect seals and cable strain before and after a controlled water-resistance check appropriate to the enclosure documentation.
  • +
  • Keep USB recovery practical and record the recovery procedure.
  • +
+ +Do not mount the build until all checks pass and a hardware reviewer approves the site record. + +## Recovery and undo + +If a check fails, disconnect solar, USB, and battery power using the documented safe sequence. Move the unit to a non-combustible supervised work area, inspect for heat or battery damage, and do not reconnect a questionable lithium battery. Restore the last known-good board configuration by USB only after the electrical fault is resolved. + +Mechanical holes and adhesive/sealant work may not be reversible; replace damaged or uncertain enclosure and power parts rather than improvising a repair. + +## Maintenance + +No Canada-wide interval is approved. Before deployment, record a site-specific inspection schedule covering mount movement, water entry, seals, corrosion, cable strain, battery condition, charging behaviour, measured values, firmware/configuration, radio verification, and physical recovery. + +## Sources and change log + +- Original community guide: MrAlders0n (Ottawa), January 1, 2026. +- July 19, 2026: restructured as a draft staged guide; added evidence limits, safety gates, accessible captions, bench commissioning, recovery, and maintenance record requirements. No hardware compatibility claim was upgraded to verified. + +## Next step + +After hardware review and bench commissioning, use the [mounting plan](repeater-mounting-options.md). Use the [repeater configurator](../config/index.md) before the final region setup. diff --git a/docs/hardware/repeater-solar-batteries.md b/docs/hardware/repeater-solar-batteries.md index e69de29..30fc399 100644 --- a/docs/hardware/repeater-solar-batteries.md +++ b/docs/hardware/repeater-solar-batteries.md @@ -0,0 +1,62 @@ +--- +title: Solar and battery planning status +description: Understand why a general Canadian solar and battery sizing guide is not yet approved for deployment decisions. +audience: + - repeater-builder + - hardware-reviewer +task: assess-repeater-power-guidance +scope: canada-baseline +status: draft +owner: docs-hardware +last_reviewed: 2026-07-19 +review_by: 2026-10-17 +difficulty: advanced +estimated_time: 3-5 minutes +destructive: false +page_styles: + - assets/styles/devices-builds.css +--- + +# Solar and Battery Planning + +This page preserves the route while a reviewed Canadian power-sizing method is developed. + +
+ +**Draft — not reviewed for deployment.** Do not use it to choose a battery, solar panel, charger, protection circuit, wiring, or enclosure. + +
+ +## Why there is no general calculator yet + +Solar and battery sizing depends on measured repeater load, local seasonal conditions, shading, panel orientation, battery chemistry and temperature limits, charger behaviour, protection, wiring losses, enclosure temperature, desired autonomy, and maintenance access. MeshCore Canada has not approved one model that covers those inputs. + +!!! danger "Stop before substituting power-system parts" + Do not improvise or substitute a battery, charger, panel, protection device, connector, or wiring path from a capacity number alone. Follow manufacturer documentation and obtain qualified electrical review for the complete system. + +## What a reviewed guide must include + +
    +
  • A measured load profile for each supported radio, firmware role, and operating mode.
  • +
  • Dated climate and seasonal assumptions with a stated location and safety margin.
  • +
  • Battery chemistry, temperature, charging, protection, enclosure, and transport limits.
  • +
  • Panel, charger, wiring, connector, fuse/protection, and conversion-loss calculations.
  • +
  • Expected electrical readings and stop limits tied to manufacturer documents.
  • +
  • Bench test, commissioning, maintenance, failure, and safe recovery procedures.
  • +
  • A named hardware/electrical reviewer and date-scoped supported component list.
  • +
+ +## Build-specific records + +The two retained community build pages contain their own dated parts and assembly notes. They are not substitutes for a reviewed Canada-wide sizing method: + +- [300 mW solar repeater draft](repeater-solar-300mw-diy-build.md) +- [1 W experimental solar repeater](repeater-solar-1w-diy-build.md) + +## Human review required + +A hardware reviewer must supply the model, sources, test evidence, supported limits, climate scenarios, and maintenance checks before this page can become operational guidance. + +## Next step + +If you already have a reviewed power plan, return to [choosing a repeater path](recommended-repeaters.md). Otherwise ask the local community for a domain reviewer before buying parts. diff --git a/docs/hardware/wire-connector-types.md b/docs/hardware/wire-connector-types.md index 87963fe..b70c20b 100644 --- a/docs/hardware/wire-connector-types.md +++ b/docs/hardware/wire-connector-types.md @@ -1,11 +1,59 @@ -# Wire Connector Types +--- +title: Wire connector reference status +description: Review an incomplete connector-name inventory and the checks required before ordering, crimping, or applying power. +audience: + - repeater-builder + - hardware-reviewer +task: identify-wire-connector +scope: ottawa-field-practice +status: draft +owner: docs-hardware +last_reviewed: 2026-07-19 +review_by: 2026-10-17 +difficulty: advanced +estimated_time: 3-5 minutes +destructive: false +page_styles: + - assets/styles/devices-builds.css +--- -*Page in progress* +# Wire Connector Reference -Here are some wire connectors used across our repeater builds: +This incomplete inventory records connector names seen in community build notes. It is not a compatibility or wiring guide. -| Connector Name | Size | Devices that use it | -|----------------|------|----------------------| -| Molex PicoBlade 1x02P | 1.25mm | Ikoka Stick | -| JST ZHR-2 | 1.5mm | RAK19007 Solar Connector | -| JST PHR-2 | 2.0mm | RAK19007 Battery Connector | \ No newline at end of file +
+ +**Draft reference — verify before wiring.** A similar shape or pitch does not prove the series, mating pair, keying, polarity, pinout, current rating, wire gauge, or crimp compatibility. + +
+ +
+ +| Connector name in prior notes | Pitch recorded | Device/use recorded | Evidence missing | +|---|---:|---|---| +| Molex PicoBlade 1×02P | 1.25 mm | Ikoka Stick | Manufacturer part numbers, mating pair, pinout, polarity, current, tooling, photograph | +| JST ZHR-2 | 1.5 mm | RAK19007 solar connector | Manufacturer part numbers, mating pair, pinout, polarity, current, tooling, photograph | +| JST PHR-2 | 2.0 mm | RAK19007 battery connector | Manufacturer part numbers, mating pair, pinout, polarity, current, tooling, photograph | + +
+ +## Before using a connector name + +
    +
  • Find the exact connector and mating part in the device schematic or manufacturer documentation.
  • +
  • Confirm the keying, contact gender, pitch, wire gauge, current, voltage, temperature, and environment ratings.
  • +
  • Confirm polarity and pinout from documentation, then verify with equipment disconnected.
  • +
  • Use the specified contacts and crimp tooling; do not force a similar-looking housing.
  • +
  • Stop if any identity, rating, pinout, or polarity detail is uncertain.
  • +
+ +!!! danger "A connector that fits can still be wrong" + Reversed polarity, a mismatched contact system, or an unsuitable wire/connector rating can damage equipment or create a battery and fire hazard. Do not apply power from this page. + +## Human review required + +This page needs dated manufacturer sources, photographs, part numbers, mating parts, pinouts, polarity, ratings, tooling, and links from each build step before it becomes an operational reference. + +## Next step + +Return to the exact [build guide](recommended-repeaters.md) and verify every connector against the hardware revision being assembled. diff --git a/docs/index.md b/docs/index.md index 767133e..6f8f6d8 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,98 +1,176 @@ --- +title: MeshCore Canada +description: Join, build, operate, and coordinate a local MeshCore network in Canada. +audience: + - newcomer + - meshcore-operator +task: choose-a-goal +scope: canada-baseline +status: verified +owner: docs-ux +last_reviewed: 2026-07-19 +review_by: 2027-07-19 +tested_with: + content_baseline: f608cfe +difficulty: beginner +estimated_time: 1-2 minutes +destructive: false hide: - toc --- + # MeshCore Canada -!!! warning "Community Project" - **meshcore.ca** is an independent community site. We're not affiliated with, endorsed by, or officially connected to the MeshCore or MeshOS projects. We're just a group of Canadians helping other Canadians organize their local meshes and build useful tools for the community. +MeshCore Canada helps Canadians join, build, operate, and coordinate local +MeshCore networks. -Canada's community hub for MeshCore, a long-range, low-power mesh protocol built on LoRa radios. +[:octicons-arrow-right-24: **Brand new? Start here**](start/index.md){ .md-button .md-button--primary } -Whether you're brand new to mesh networking or looking to deploy repeaters across your region, you'll find guides, hardware recommendations, and your local community here. +## What are you trying to do?
-- :fontawesome-brands-discord:{ .lg .middle } **Discord** +- :material-message-text:{ .lg .middle } **Start using MeshCore** --- - Join the MeshCore Canada Discord to chat with the community, ask questions, and stay up to date. + Choose a role and follow a clear path from preparation to a working check. - [:octicons-arrow-right-24: Join Discord](https://discord.gg/BESFVMt7yk){ target="_blank" rel="noopener" } + [:octicons-arrow-right-24: Start](start/index.md) -- :fontawesome-solid-comments:{ .lg .middle } **Forum** +- :material-map-marker-radius:{ .lg .middle } **Find a community** --- - Browse discussions, share builds, and find answers on the MeshCore Canada community forum. + Find local contacts and check whether your area publishes different + settings. - [:octicons-arrow-right-24: Visit Forum](https://forum.meshcore.ca){ target="_blank" rel="noopener" } + [:octicons-arrow-right-24: Find a community](provinces/index.md) -
+- :material-access-point-network:{ .lg .middle } **Set up or operate a node** ---- + --- + + Go directly to the companion, repeater, room-server, or observer path. + + [:octicons-arrow-right-24: Choose a node role](start/choose-a-goal.md) + +
-## Explore the Site +## Choose a role
-- :material-access-point-network:{ .lg .middle } **MeshCore** +- :material-cellphone-link:{ .lg .middle } **Personal companion** - --- + Send and receive messages. - Learn what MeshCore is, browse the FAQ, and find recommended hardware for companions, repeaters, and antennas. + [:octicons-arrow-right-24: Set up a companion](start/companion.md) - [:octicons-arrow-right-24: Introduction](meshcore/general-overview.md) +- :material-radio-tower:{ .lg .middle } **Repeater** -- :material-chart-timeline-variant:{ .lg .middle } **Analyzer & MQTT** + Improve local coverage. - --- + [:octicons-arrow-right-24: Set up a repeater](start/repeater.md) - Set up packet analysis and MQTT bridging with guides for MCtoMQTT, MeshCore-HA, MQTT Firmware, and PyMC. +- :material-forum:{ .lg .middle } **Room server** - [:octicons-arrow-right-24: Overview](analyzer/intro.md) + Host a persistent room. -- :material-map-search:{ .lg .middle } **Canadian Regions** + [:octicons-arrow-right-24: Set up a room server](start/room-server.md) - --- +- :material-chart-timeline-variant:{ .lg .middle } **Observer** - Find the right region for a repeater, follow the setup guide, or explore the map. + Send network data to the public tools. - [:octicons-arrow-right-24: Open Config](config/index.md) + [:octicons-arrow-right-24: Set up an observer](start/observer.md) -- :material-map-marker-radius:{ .lg .middle } **Mesh Directory** +
- --- +Not sure which one fits? [Compare the roles](start/choose-a-goal.md). - Find a MeshCore community near you. Browse by province to discover local networks, frequencies, and contacts. +## Canada baseline - [:octicons-arrow-right-24: Find Your Province](provinces/index.md) +Use these settings when a local community has not published an override. -- :material-bookshelf:{ .lg .middle } **Resources** +| Setting | Canada baseline | +|---|---| +| Radio preset | **USA/Canada (Recommended)** | +| Raw radio values | `910.525 MHz / 62.5 kHz / SF7 / CR5` | +| Path setting | **3-byte** | +| Command-line path setting | `set path.hash.mode 2` | - --- +!!! warning "Check your local community first" + Nearby devices must use matching settings. If your local community page + publishes an override, use its settings instead of the Canada baseline. - New here? Start with the Getting Started guide, browse useful links, or check the glossary. +## Find your region - [:octicons-arrow-right-24: Getting Started](resources/getting-started.md) +Search by city, airport code, postal code, or region name. - + ---- +Or [browse the region map](config/map.md). -## Who We Are +Looking for people nearby? [Browse communities](provinces/index.md), ask on the +[MeshCore Canada forum](https://forum.meshcore.ca/) or +[Discord](https://discord.gg/BESFVMt7yk). These are support options; you can +start the setup guide without joining either service. -We are a group of Canadian meshes across the country from British Columbia, Alberta, Ontario, and Quebec (hopefully more soon) that are working together to bring services and standards across Canada for all MeshCore users. We host MQTT servers and a packet analyzer at this site for all Canadians to use. +## Use network tools -The servers and services are currently managed by [**Mr. Alderson**](https://github.com/MrAlders0n), [**Ded**](https://github.com/446564), [**n30nex**](https://github.com/n30nex), and [**Kranic**](https://forum.meshcore.ca/u/djkranic). +
-The documentation on this site is open to all to contribute to and is backed by markdown files in GitHub at [MeshCore-ca/MeshCore-Canada](https://github.com/MeshCore-ca/MeshCore-Canada). +- **Configure a repeater** ---- + Find the regional settings and commands for a repeater. + + [:octicons-arrow-right-24: Open the configurator](config/index.md) + +- **Explore regions** + + Search and inspect the Canadian region map. + + [:octicons-arrow-right-24: Open the region map](config/map.md) + +- **Check the live network** + + View public nodes, observers, and packet activity in CoreScope. + + [:octicons-link-external-24: Open CoreScope](https://live.meshcore.ca/) + +- **Set up an observer** + + Choose how to send network data to the public tools. + + [:octicons-arrow-right-24: Choose an observer method](analyzer/intro.md) + +
+ +## Improve MeshCore Canada + +Found missing, confusing, or outdated information? +[Share an idea](submit-idea.md) or [update a community +listing](contributing.md). -## Want to Contribute? +## About this project -This site is community-driven. You do not need to know Git, have a GitHub account, or understand MeshCore terminology to help. Use the [Share an Idea](submit-idea.md) page to describe what you noticed or what would make the project better. After you review it, the page can create a public review issue automatically. Forum, Discord, copy, and manual GitHub options remain available. +MeshCore Canada is an independent community project. It is not affiliated +with, endorsed by, or officially connected to the MeshCore or MeshOS +projects. -Experienced contributors can still use the [Contributing](contributing.md) page for issue templates and pull requests. +The documentation is maintained in the +[MeshCore Canada repository](https://github.com/MeshCore-ca/MeshCore-Canada). +See [how to contribute](contributing.md) for maintainers and contribution +options. diff --git a/docs/javascripts/submission-form.js b/docs/javascripts/submission-form.js index 9953de6..cc48cbf 100644 --- a/docs/javascripts/submission-form.js +++ b/docs/javascripts/submission-form.js @@ -3,6 +3,7 @@ const form = document.getElementById("community-submission-form"); if (!form) return; + form.noValidate = true; const scriptUrl = document.currentScript && document.currentScript.src ? document.currentScript.src @@ -11,21 +12,41 @@ const transportModuleUrl = new URL("../config/editor/issue.js", scriptUrl).href; const elements = { preview: document.getElementById("submission-preview"), + previewNote: document.getElementById("submission-preview-note"), status: document.getElementById("submission-status"), review: document.getElementById("review-submission"), + reviewAgain: document.getElementById("review-submission-again"), copy: document.getElementById("copy-submission"), submit: document.getElementById("submit-community-idea"), github: document.getElementById("open-github-submission"), githubNote: document.getElementById("submission-github-note"), result: document.getElementById("submission-result"), + errorSummary: document.getElementById("submission-error-summary"), verification: document.getElementById("submission-verification"), finalActions: document.getElementById("submission-final-actions"), turnstile: document.getElementById("submission-turnstile"), antiSpamStatus: document.getElementById("submission-anti-spam-status"), antiSpamRetry: document.getElementById("submission-anti-spam-retry"), - website: document.getElementById("submission-website") + website: document.getElementById("submission-website"), + publicConsent: document.getElementById("submission-public"), + saveDraft: document.getElementById("save-submission-draft"), + clearDraft: document.getElementById("clear-submission-draft"), + draftStatus: document.getElementById("submission-draft-status"), + stages: Array.from(document.querySelectorAll("[data-submission-stage]")) }; + const DRAFT_KEY = "meshcore-canada:community-idea-draft:v1"; + const DRAFT_FIELD_IDS = [ + "submission-category", + "submission-experience", + "submission-summary", + "submission-region", + "submission-need", + "submission-idea", + "submission-context", + "submission-follow-up" + ]; + let modules = null; let config = null; let turnstile = null; @@ -38,6 +59,185 @@ let preparedRevision = -1; let preparedProposal = null; let preparedText = ""; + let draftOptedIn = false; + let draftTimer = null; + let receiptVisible = false; + + function setStage(stage, complete = false) { + const order = ["review", "verify", "submit"]; + const currentIndex = order.indexOf(stage); + elements.stages.forEach((item) => { + const itemIndex = order.indexOf(item.dataset.submissionStage); + item.toggleAttribute("aria-current", !complete && itemIndex === currentIndex); + item.dataset.complete = String(complete ? itemIndex <= currentIndex : itemIndex < currentIndex); + }); + } + + function addDescription(field, id) { + const ids = new Set((field.getAttribute("aria-describedby") || "").split(/\s+/).filter(Boolean)); + ids.add(id); + field.setAttribute("aria-describedby", Array.from(ids).join(" ")); + } + + function removeDescription(field, id) { + const ids = (field.getAttribute("aria-describedby") || "") + .split(/\s+/) + .filter((value) => value && value !== id); + if (ids.length) field.setAttribute("aria-describedby", ids.join(" ")); + else field.removeAttribute("aria-describedby"); + } + + function clearFieldError(field) { + if (!field || !field.id) return; + const errorId = `${field.id}-error`; + document.getElementById(errorId)?.remove(); + field.removeAttribute("aria-invalid"); + removeDescription(field, errorId); + } + + function clearValidationErrors() { + form.querySelectorAll(".submission-field-error").forEach((error) => error.remove()); + form.querySelectorAll("[aria-invalid='true']").forEach((field) => { + field.removeAttribute("aria-invalid"); + removeDescription(field, `${field.id}-error`); + }); + elements.errorSummary.hidden = true; + elements.errorSummary.querySelector("ul").replaceChildren(); + } + + function fieldLabel(field) { + const label = form.querySelector(`label[for='${CSS.escape(field.id)}']`); + return (label?.textContent || field.name || "This field").replace(/\s+/g, " ").trim(); + } + + function showValidationErrors() { + clearValidationErrors(); + const invalidFields = Array.from(form.querySelectorAll("[required]")).filter( + (field) => !field.validity.valid + ); + if (!invalidFields.length) return true; + + const list = elements.errorSummary.querySelector("ul"); + invalidFields.forEach((field) => { + const label = fieldLabel(field); + const message = field.validity.valueMissing ? `${label} is required.` : field.validationMessage; + const error = document.createElement("p"); + error.id = `${field.id}-error`; + error.className = "submission-field-error"; + error.textContent = message; + const container = field.closest(".submission-field"); + if (container) container.append(error); + else field.closest(".submission-consent")?.insertAdjacentElement("afterend", error); + field.setAttribute("aria-invalid", "true"); + addDescription(field, error.id); + + const item = document.createElement("li"); + const link = document.createElement("a"); + link.href = `#${field.id}`; + link.textContent = message; + link.addEventListener("click", () => window.setTimeout(() => field.focus(), 0)); + item.append(link); + list.append(item); + }); + elements.errorSummary.hidden = false; + elements.errorSummary.focus(); + return false; + } + + function initialiseCharacterCounters() { + form.querySelectorAll("input[maxlength], textarea[maxlength]").forEach((field) => { + if (!field.id || field === elements.website || field.maxLength < 0) return; + const counter = document.createElement("span"); + counter.id = `${field.id}-count`; + counter.className = "submission-character-count"; + const update = () => { + counter.textContent = `${field.value.length} / ${field.maxLength}`; + counter.dataset.nearLimit = String(field.value.length >= field.maxLength * 0.9); + }; + field.insertAdjacentElement("afterend", counter); + addDescription(field, counter.id); + field.addEventListener("input", update); + update(); + }); + } + + function draftValues() { + return Object.fromEntries(DRAFT_FIELD_IDS.map((id) => [id, document.getElementById(id).value])); + } + + function saveDraft(quiet = false) { + if (!elements.publicConsent.checked) { + elements.draftStatus.textContent = "Confirm the public-content statement before saving a draft."; + return false; + } + try { + window.localStorage.setItem(DRAFT_KEY, JSON.stringify({ + version: 1, + savedAt: new Date().toISOString(), + fields: draftValues() + })); + draftOptedIn = true; + elements.clearDraft.hidden = false; + if (!quiet) elements.draftStatus.textContent = "Draft saved on this device. Turnstile and anti-spam values are never stored."; + return true; + } catch (_error) { + elements.draftStatus.textContent = "This browser blocked draft storage. Your answers remain in the form."; + return false; + } + } + + function scheduleDraftSave() { + if (!draftOptedIn || !elements.publicConsent.checked) return; + if (draftTimer) window.clearTimeout(draftTimer); + draftTimer = window.setTimeout(() => { + saveDraft(true); + elements.draftStatus.textContent = "Saved draft updated on this device."; + }, 300); + } + + function clearSavedDraft(message = "Saved draft cleared.") { + if (draftTimer) window.clearTimeout(draftTimer); + draftTimer = null; + try { window.localStorage.removeItem(DRAFT_KEY); } catch (_error) {} + draftOptedIn = false; + elements.clearDraft.hidden = true; + elements.draftStatus.textContent = message; + } + + function restoreDraft() { + try { + const raw = window.localStorage.getItem(DRAFT_KEY); + if (!raw) return; + const draft = JSON.parse(raw); + if (draft?.version !== 1 || !draft.fields || typeof draft.fields !== "object") { + clearSavedDraft("An incompatible saved draft was removed."); + return; + } + DRAFT_FIELD_IDS.forEach((id) => { + const field = document.getElementById(id); + const saved = draft.fields[id]; + if (typeof saved !== "string") return; + const bounded = field.maxLength > -1 ? saved.slice(0, field.maxLength) : saved; + if (field instanceof HTMLSelectElement) { + if (Array.from(field.options).some((option) => option.value === bounded)) field.value = bounded; + } else field.value = bounded; + }); + draftOptedIn = true; + elements.clearDraft.hidden = false; + elements.publicConsent.checked = false; + elements.draftStatus.textContent = "Draft restored. Confirm the public-content statement before updating or submitting it."; + } catch (_error) { + clearSavedDraft("The saved draft could not be read and was removed."); + } + } + + function updateDraftControls() { + elements.saveDraft.disabled = !elements.publicConsent.checked; + if (!elements.publicConsent.checked && !draftOptedIn) { + elements.draftStatus.textContent = "Confirm the public-content statement before saving a draft."; + } else if (elements.publicConsent.checked && draftOptedIn) scheduleDraftSave(); + else if (elements.publicConsent.checked) elements.draftStatus.textContent = "You can now save a draft on this device."; + } async function loadModules() { if (!modules) { @@ -51,14 +251,22 @@ } function updateActions() { - const current = preparedProposal && preparedRevision === revision; + const current = Boolean(preparedProposal && preparedRevision === revision); + const hasPreview = Boolean(preparedText); + const stale = hasPreview && !current; form.dataset.prepared = current ? "true" : "false"; - elements.review.textContent = current ? "Update preview" : "Review idea"; + form.dataset.previewStale = stale ? "true" : "false"; + elements.review.textContent = current ? "Update preview" : stale ? "Review changes again" : "Review idea"; elements.review.classList.toggle("md-button--primary", !current); + elements.preview.hidden = !hasPreview; + elements.previewNote.hidden = !hasPreview; elements.verification.hidden = !current; - elements.finalActions.hidden = !current; + elements.finalActions.hidden = !hasPreview; + elements.reviewAgain.hidden = !stale; elements.submit.disabled = !current || !config || !token || submitting; elements.copy.disabled = !current || submitting; + if (receiptVisible) setStage("submit", true); + else setStage(current ? (token ? "submit" : "verify") : "review"); } function setAntiSpamStatus(message, state = "") { @@ -67,6 +275,7 @@ } function clearResult() { + receiptVisible = false; elements.result.replaceChildren(); elements.result.dataset.state = ""; } @@ -82,8 +291,14 @@ link.target = "_blank"; link.rel = "noopener"; link.textContent = `Open issue #${value.issueNumber}`; - elements.result.replaceChildren(document.createTextNode(prefix), link); + elements.result.replaceChildren( + document.createTextNode(prefix), + link, + document.createTextNode(". Maintainers will review the public issue and respond there.") + ); elements.result.dataset.state = "success"; + receiptVisible = true; + elements.result.focus(); } function values() { @@ -108,15 +323,13 @@ function markUnprepared() { preparedProposal = null; - preparedText = ""; preparedRevision = -1; elements.github.href = "#"; elements.github.classList.add("is-disabled"); elements.github.setAttribute("aria-disabled", "true"); - elements.preview.hidden = true; clearGithubNote(); clearResult(); - if (!submitting) elements.status.textContent = "Answers changed. Review again."; + if (!submitting) elements.status.textContent = "Answers changed. Review them again before submitting."; updateActions(); } @@ -237,14 +450,16 @@ form.addEventListener("submit", async (event) => { event.preventDefault(); - if (!form.reportValidity()) return; + if (!showValidationErrors()) { + elements.status.textContent = "Fix the fields listed above, then review again."; + return; + } try { const loaded = await loadModules(); preparedProposal = loaded.community.buildCommunityIdea(values()); preparedText = loaded.community.buildSubmissionText(preparedProposal); preparedRevision = revision; elements.preview.textContent = preparedText; - elements.preview.hidden = false; clearResult(); const manual = loaded.community.buildManualGithubLink(preparedProposal); @@ -260,8 +475,8 @@ updateActions(); if (!config || !token) void initialiseSubmission(); elements.status.textContent = config && token - ? "Ready to submit." - : "Preview ready."; + ? "Preview ready. Nothing has been submitted. You can now submit." + : "Preview ready. Nothing has been submitted. Complete the verification next."; const reducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches; elements.preview.scrollIntoView({ behavior: reducedMotion ? "auto" : "smooth", block: "nearest" }); } catch (error) { @@ -270,11 +485,20 @@ } }); - form.addEventListener("input", () => { + form.addEventListener("input", (event) => { + if (event.target === elements.website) return; + clearFieldError(event.target); + if (!form.querySelector(".submission-field-error")) elements.errorSummary.hidden = true; revision += 1; if (preparedProposal) markUnprepared(); + scheduleDraftSave(); }); + elements.publicConsent.addEventListener("change", updateDraftControls); + elements.saveDraft.addEventListener("click", () => saveDraft(false)); + elements.clearDraft.addEventListener("click", () => clearSavedDraft()); + elements.reviewAgain.addEventListener("click", () => form.requestSubmit(elements.review)); + elements.copy.addEventListener("click", async () => { if (!preparedText || preparedRevision !== revision) return; try { @@ -315,6 +539,7 @@ }); const changed = revision !== submittedRevision; showResult(value, changed); + if (!changed) clearSavedDraft("Submitted. Saved draft cleared from this device."); elements.status.textContent = changed ? "The reviewed version was submitted. Review and submit again to include your newer changes." : value.duplicate @@ -322,8 +547,8 @@ : "Submitted for review."; } catch (error) { const nextStep = error.retryable - ? " Your answers are saved here. Complete the check and try again." - : " Copy the text or use GitHub."; + ? " Your answers remain in the form. Complete the check and try again." + : " Your answers remain in the form. Copy the text or use GitHub."; elements.status.textContent = (error.message || "The idea could not be submitted.") + nextStep; } finally { submitting = false; @@ -342,5 +567,8 @@ }); elements.antiSpamRetry.addEventListener("click", initialiseSubmission); + initialiseCharacterCounters(); + restoreDraft(); + updateDraftControls(); updateActions(); })(); diff --git a/docs/meshcore/firmware-heltec-v3-wifi.md b/docs/meshcore/firmware-heltec-v3-wifi.md index 65e4fcf..5b1e1d2 100644 --- a/docs/meshcore/firmware-heltec-v3-wifi.md +++ b/docs/meshcore/firmware-heltec-v3-wifi.md @@ -1,132 +1,59 @@ -# Compiling MeshCore Firmware with Wi-Fi Enabled - -**Device:** Heltec V3 -**OS:** Red Hat 9.6 - -**Note:** -Wi-Fi support in MeshCore is experimental. -Your SSID and password are embedded at compile time, so do **not** share compiled binaries that contain your real credentials. - --- - -## Install PlatformIO - -1. Change to your home directory: - - ```bash - cd ~ - ``` - -2. Download the PlatformIO installer: - - ```bash - curl -fsSL -o get-platformio.py https://raw.githubusercontent.com/platformio/platformio-core-installer/master/get-platformio.py - ``` - -3. Run the installer: - - ```bash - python3 get-platformio.py - ``` - -4. Add PlatformIO to your PATH (adjust the path if your username is different): - - ```bash - export PATH=$PATH:/home/linuxuser/.platformio/penv/bin - ``` - -## Clone the MeshCore Repository - -1. Clone the MeshCore repo: - - ```bash - git clone https://github.com/ripplebiz/MeshCore.git - ``` - -2. Change into the project directory: - - ```bash - cd MeshCore/ - ``` - +title: Heltec V3 Wi-Fi firmware experimental archive +description: Understand why the former Heltec V3 Wi-Fi build recipe is archived and what evidence is needed to restore it. +audience: + - firmware-maintainer + - advanced-builder +task: review-heltec-wifi-archive +scope: experimental +status: archived +owner: docs-firmware +last_reviewed: 2026-07-19 +review_by: 2027-01-19 +difficulty: advanced +estimated_time: 3-5 minutes +destructive: false +page_styles: + - assets/styles/devices-builds.css --- +# Heltec V3 Wi-Fi Firmware (Experimental Archive) -## Configure Wi-Fi Credentials - -1. Open the PlatformIO configuration for the Heltec V3 Wi-Fi build: - - ```bash - vi variants/heltec_v3/platformio.ini - ``` - -2. Locate the `env:Heltec_v3_companion_radio_wifi` section and update it with your SSID and password: +
-```ini -[env:Heltec_v3_companion_radio_wifi] -extends = Heltec_lora32_v3 -build_flags = - ${Heltec_lora32_v3.build_flags} - -D MAX_CONTACTS=100 - -D MAX_GROUP_CHANNELS=8 - -D DISPLAY_CLASS=SSD1306Display - -D WIFI_DEBUG_LOGGING=1 - -D WIFI_SSID="<>" - -D WIFI_PWD="<>" -``` +**Archived route.** It contains no supported build or flash recipe. The unsafe-to-trust historical steps remain removed while a reproducible, secret-safe current build awaits review. -3. Save and exit the editor. +
-## Compile and Prepare Firmware +!!! danger "Experimental and unverified — do not deploy" + The former recipe referenced an old repository path, embedded Wi-Fi credentials in compiled firmware, and used version examples that were not tied to a reviewed build. Those steps were removed from the operational guide. -1. Set the firmware version environment variable: +- **Scope:** Historical Heltec V3 experiment +- **Status:** Archived pending reproducible build and security review - ```bash - set FIRMWARE_VERSION=1.7.3 - ``` +This URL is retained for existing links. It does not currently provide a supported MeshCore Canada firmware build. - *(Or use `export FIRMWARE_VERSION=1.7.3` if you are using a pure Linux shell and not a mixed environment.)* +## Why the old recipe was quarantined -2. Build the Wi-Fi firmware target: +- Its source and version were not pinned to a reviewed commit. +- Network credentials were compiled into the binary, making shared artifacts unsafe. +- It lacked artifact checksums, a tested-device matrix, expected logs, and a recovery procedure. +- Experimental firmware appeared beside ordinary beginner flashing guides without a clear support boundary. - ```bash - ./build.sh build-firmware Heltec_v3_companion_radio_wifi - ``` +## Current safe path -3. Change into the build output directory: - - ```bash - cd .pio/build/Heltec_v3_companion_radio_wifi/ - ``` - -4. Rename the output binaries: - - ```bash - mv firmware-merged.bin Heltec_v3_companion_radio_wifi_1.7.3-merged.bin - mv firmware.bin Heltec_v3_companion_radio_wifi_1.7.3.bin - ``` - -5. Move the generated firmware files to a convenient location (example): - - ```bash - mv Heltec_v3_companion_radio_wifi* /home/linuxuser/ - ``` - ---- +Use the [official MeshCore source repository](https://github.com/meshcore-dev/MeshCore) and the [official MeshCore web flasher](https://meshcore.io/flasher) for supported targets. Do not treat a Wi-Fi target as supported unless it appears in the current upstream project and has been reviewed for your exact board and role. -## Next Steps +## Historical references retained for review -1. Flash one of the compiled firmware files onto your Heltec V3: +- [Official MeshCore source repository](https://github.com/meshcore-dev/MeshCore) +- [Historical upstream repository URL used by the old guide](https://github.com/ripplebiz/MeshCore.git) +- [PlatformIO Core installer source](https://raw.githubusercontent.com/platformio/platformio-core-installer/master/get-platformio.py) - - `Heltec_v3_companion_radio_wifi_1.7.3.bin` - - or `Heltec_v3_companion_radio_wifi_1.7.3-merged.bin` +## Human review required -2. Connect to the device over serial and monitor logs to confirm: +Reinstating this guide requires a named source commit, supported board/role target, secret-safe configuration method, reproducible build commands, artifact checksums, expected serial output, bench verification, limitations, and recovery instructions. - - Wi-Fi is enabled - - The device is attempting to associate with your SSID -3. Remember that Wi-Fi support in MeshCore is experimental: +## Next step - - Expect instability - - Features may be incomplete - - Do not deploy this build as a critical node on the mesh +For an ordinary supported device, use the [official MeshCore web flasher](https://meshcore.io/flasher) and the matching [companion](flash-companion.md) or [repeater](flash-repeater.md) safety flow. diff --git a/docs/meshcore/firmware-rak-custom-display.md b/docs/meshcore/firmware-rak-custom-display.md index dd52f1b..71a04ca 100644 --- a/docs/meshcore/firmware-rak-custom-display.md +++ b/docs/meshcore/firmware-rak-custom-display.md @@ -1,177 +1,55 @@ -# Compiling MeshCore Firmware for RAK4631 with MakerFocus 1.3" OLED (SH1106) - -**Device:** RAK4631 with MakerFocus 1.3" SH1106 OLED -**OS:** Red Hat 9.6 - -**Note:** -MeshCore does not natively support the MakerFocus SH1106 OLED on the RAK4631. -The steps below modify the PlatformIO configuration to add SH1106 display support. - --- +title: RAK4631 custom display firmware archive +description: Understand why the former RAK4631 custom-display recipe is archived and what proof is needed to restore it. +audience: + - firmware-maintainer + - advanced-builder +task: review-rak-display-archive +scope: experimental +status: archived +owner: docs-firmware +last_reviewed: 2026-07-19 +review_by: 2027-01-19 +difficulty: advanced +estimated_time: 3-5 minutes +destructive: false +page_styles: + - assets/styles/devices-builds.css +--- +# RAK4631 Custom Display Firmware (Archived) -## Install PlatformIO - -1. Change to your home directory: - - ```bash - cd ~ - ``` - -2. Download the PlatformIO installer: - - ```bash - curl -fsSL -o get-platformio.py https://raw.githubusercontent.com/platformio/platformio-core-installer/master/get-platformio.py - ``` - -3. Run the installer: - - ```bash - python3 get-platformio.py - ``` - -4. Add PlatformIO to your PATH (adjust the path if your username is different): - - ```bash - export PATH=$PATH:/home/linuxuser/.platformio/penv/bin - ``` - -## Clone the MeshCore Repository - -1. Clone the MeshCore repo: - - ```bash - git clone https://github.com/ripplebiz/MeshCore.git - ``` - -2. Change into the project directory: - - ```bash - cd MeshCore/ - ``` - -## 3. Edit the PlatformIO Configuration - -1. Open the RAK4631 PlatformIO configuration file: - -```bash -vi variants/rak4631/platformio.ini -``` - -1. Replace OLED-related lines as shown below. -Every line marked with “<============= CHANGED” is different from the default configuration. - -```ini -[rak4631] -extends = nrf52_base -platform = https://github.com/maxgerhardt/platform-nordicnrf52.git#rak -board = wiscore_rak4631 -board_check = true -build_flags = ${nrf52_base.build_flags} - ${sensor_base.build_flags} - -I variants/rak4631 - -D RAK_4631 - -D RAK_BOARD - -D PIN_BOARD_SCL=14 - -D PIN_BOARD_SDA=13 - -D PIN_GPS_TX=16 - -D PIN_GPS_RX=15 - -D PIN_GPS_EN=-1 - -D PIN_OLED_RESET=-1 - -D RADIO_CLASS=CustomSX1262 - -D WRAPPER_CLASS=CustomSX1262Wrapper - -D LORA_TX_POWER=22 - -D SX126X_CURRENT_LIMIT=140 - -D SX126X_RX_BOOSTED_GAIN=1 -build_src_filter = ${nrf52_base.build_src_filter} - +<../variants/rak4631> - + -; + <============= CHANGED - + <============= CHANGED - + -lib_deps = - ${nrf52_base.lib_deps} - ${sensor_base.lib_deps} -; adafruit/Adafruit SSD1306 @ ^2.5.13 <============= CHANGED - sparkfun/SparkFun u-blox GNSS Arduino Library@^2.2.27 - adafruit/Adafruit SH110X @ ^2.1.13 <============= CHANGED - adafruit/Adafruit GFX Library @ ^1.12.1 <============= CHANGED - - -[env:RAK_4631_companion_radio_ble] -extends = rak4631 -board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld -board_upload.maximum_size = 712704 -build_flags = - ${rak4631.build_flags} - -I examples/companion_radio/ui-new - -D PIN_USER_BTN=9 - -D PIN_USER_BTN_ANA=31 -; -D DISPLAY_CLASS=SSD1306Display <============= CHANGED - -D DISPLAY_CLASS=SH1106Display <============= CHANGED - -D MAX_CONTACTS=350 - -D MAX_GROUP_CHANNELS=40 - -D BLE_PIN_CODE=123456 - -D BLE_DEBUG_LOGGING=1 - -D OFFLINE_QUEUE_SIZE=256 -; -D MESH_PACKET_LOGGING=1 -; -D MESH_DEBUG=1 -build_src_filter = ${rak4631.build_src_filter} - + - +<../examples/companion_radio/*.cpp> - +<../examples/companion_radio/ui-new/*.cpp> -lib_deps = - ${rak4631.lib_deps} - densaugeo/base64 @ ~1.4.0 -``` - -## Compile and Prepare Firmware - -1. Set the firmware version environment variable: - - ```bash - set FIRMWARE_VERSION=1.7.3 - ``` - - *(Or use `export FIRMWARE_VERSION=1.7.3` if you are using a pure Linux shell and not a mixed environment.)* - -2. Build the Wi-Fi firmware target: +
- ```bash - ./build.sh build-firmware RAK_4631_companion_radio_ble - ``` +**Archived route.** It contains no supported build or flash recipe. Conflicting historical repository, target, device, filename, and version details remain quarantined. -3. Change into the build output directory: +
- ```bash - cd .pio/build/RAK_4631_companion_radio_ble/ - ``` +!!! danger "Archived and unverified — do not flash" + The former instructions on this page mixed repository, target, device, filename, and firmware-version details. They have not been reproduced against the current official MeshCore source, so they were removed from the operational guide rather than presented as working instructions. -4. Rename the output binaries: +- **Scope:** Historical community experiment +- **Status:** Archived; not supported or verified by MeshCore Canada - ```bash - mv firmware-merged.bin RAK_4631_companion_radio_ble_1.9.0-merged.bin - mv firmware.bin RAK_4631_companion_radio_ble_1.9.0.bin - ``` +The experiment attempted to add a MakerFocus 1.3-inch SH1106 OLED to a RAK4631 companion build. There is currently no reviewed build recipe, supported-version matrix, test log, or recovery procedure on this page. -5. Move the generated firmware files to a convenient location (example): +## Do not use this page to build or flash firmware - ```bash - mv AK_4631_companion_radio_ble* /home/linuxuser/ - ``` +- Do not copy old PlatformIO configuration fragments from cached versions of this page. +- Do not flash an artifact unless its board target, source commit, build command, checksum, and recovery path have been independently verified. +- Use the [official MeshCore source repository](https://github.com/meshcore-dev/MeshCore) for current supported targets. -## Next Steps +## Historical references retained for review -1. Flash one of the compiled firmware files onto your Heltec V3: +- [Official MeshCore source repository](https://github.com/meshcore-dev/MeshCore) +- [Historical upstream repository URL used by the old guide](https://github.com/ripplebiz/MeshCore.git) +- Historical PlatformIO dependency URL used by the old guide: +- [PlatformIO Core installer source](https://raw.githubusercontent.com/platformio/platformio-core-installer/master/get-platformio.py) - - `RAK_4631_companion_radio_ble_1.9.0.bin` - - or `...merged.bin` +## Human review required -2. Connect the MakerFocus SH1106 OLED at **I²C address 0x3C** +Restoring an operational guide requires a clean build from a named official commit, a verified RAK4631 target, a documented display wiring/power review, checksums for the resulting artifacts, a bench test, and a peer-reviewed recovery procedure. - - Ensure the jumper is on the **0x7A** side -3. Power the display using **3.3 V only** +## Next step - - The module is **NOT** 5 V tolerant - -4. On boot, the SH1106 display should now initialize normally +For a supported RAK4631 role, confirm the exact current target in the [official MeshCore web flasher](https://meshcore.io/flasher) and follow the matching [companion](flash-companion.md) or [repeater](flash-repeater.md) safety flow. diff --git a/docs/meshcore/flash-companion.md b/docs/meshcore/flash-companion.md index c2da2bb..e2ce387 100644 --- a/docs/meshcore/flash-companion.md +++ b/docs/meshcore/flash-companion.md @@ -1,47 +1,115 @@ -# Flashing and Configuring a Companion Node +--- +title: Flash and configure a companion +description: Back up, flash, configure, verify, and recover a supported MeshCore companion without losing important identity data. +audience: + - first-time-user + - companion-owner +task: flash-companion +scope: canada-baseline +status: draft +owner: docs-firmware +last_reviewed: 2026-07-19 +review_by: 2026-10-17 +difficulty: beginner +estimated_time: 20-30 minutes +destructive: true +requires: + - supported-companion + - data-capable-usb-cable +page_styles: + - assets/styles/devices-builds.css +--- +# Flash and Configure a Companion -This guide will help you flash a node and configure it to operate as a companion. +
---- +**Safety-flow reviewed; device compatibility not yet verified.** Confirm the exact board and current companion target in the official flasher. No tested board/firmware matrix is attached to this page. -## Flashing a Companion Node +
-!!! warning "USB Serial Drivers" - You may need to install drivers on your computer to connect to your device. +Flash a supported device with companion firmware, then connect it to the Canadian mesh. -The easiest way to flash a MeshCore-supported node is by using the official web flasher tool in **Google Chrome**: +- **Scope:** MeshCore Canada onboarding baseline +- **Status:** Operational guide; confirm the exact board and current firmware in the flasher -**[MeshCore Web Flasher](https://meshcore.io/flasher)** -Only some Chromium-based browsers (e.g., Google Chrome, Microsoft Edge) support the [serial connection](https://developer.mozilla.org/en-US/docs/Web/API/Web_Serial_API#browser_compatibility) required for flashing. +Local communities may publish different radio settings. Check the [Mesh Directory](../provinces/index.md) before applying the national baseline. -### Steps +## Before You Erase -1. Plug your device into your computer via USB. -2. Open the **MeshCore Web Flasher**. -3. Select your device hardware. -4. Select the firmware type: **Companion Radio (Bluetooth)**. -5. Click **Enter DFU Mode**. -6. Click **Erase Flash**. -7. Click **Flash** to install the MeshCore firmware. +!!! danger "Erase Flash deletes the device's stored data" + Erasing can remove the node identity/private key, name, contacts, channels, radio settings, and other saved configuration. An erased identity cannot be recovered unless you backed it up. -**Note:** -If the flash step fails after erasing, refresh the page, click **Enter DFU Mode** again, then click **Flash** to retry. +If this is not a brand-new device: ---- +1. Connect with the app or configuration tool that currently manages it. +2. Record the board model, role, firmware version, device name, radio settings, and any settings you need to recreate. +3. Export or securely record the device identity/private key using a supported backup method for that firmware. Store it as a secret; never post it in screenshots, chat, issues, or logs. +4. Export contacts or channels if your app provides that option. -## Configuring a Companion Node +If you cannot back up an identity or configuration that matters, **stop before Erase Flash** and ask your local community for help. -After flashing, follow these steps to complete setup: +## Prerequisites and preflight -1. Pair the node with your phone or computer (usually over Bluetooth). -2. Give the node a descriptive **name** (for example: your callsign, location, or handle). -3. Set the Ottawa frequency defaults: - **910.525 MHz / BW: 62.5 kHz / SF7 / CR5** -4. Test the node by sending a message in the **Public channel**. - - If a repeater hears you, the message will show **Heard X Repeats** instead of just **Sent**. +- [ ] I selected the exact hardware model printed on or reported by the board. +- [ ] I backed up everything I need from an existing device. +- [ ] I have a known-good data USB cable and stable power. +- [ ] I am using a browser with Web Serial support, such as current Chrome or Edge. +- [ ] I know how to put this board back into DFU or bootloader mode if flashing fails. -**Tip:** -You may want to disable **Message Settings → Auto Reset Path**. -This isn’t required, but many users find it useful when testing unstable links because it prevents the path from constantly resetting. +!!! warning "USB serial drivers" + Some boards require a USB serial driver before the browser can connect. ---- +## What this will change + +Flashing replaces the installed firmware and, when **Erase Flash** is selected, deletes stored identity and configuration. The later setup writes the device name and local radio settings. + +## Flash the companion firmware + +Use the official [MeshCore Web Flasher](https://meshcore.io/flasher). The flasher requires a browser that supports [Web Serial](https://developer.mozilla.org/en-US/docs/Web/API/Web_Serial_API#browser_compatibility). + +1. Connect the device by USB. +2. Select the exact hardware model. +3. Select **Companion Radio (Bluetooth)**. +4. Select the firmware version intended for that board. +5. Click **Enter DFU Mode**. The flasher should report that it found a device in the expected mode. +6. Recheck the hardware and firmware selections. +7. Click **Erase Flash**, then wait for a successful erase message. +8. Click **Flash** and wait for the completion message before disconnecting the device. + +## Recovery if flashing fails + +Do not keep erasing. Leave the device connected, refresh the flasher, return the board to DFU mode, reselect the exact board and companion firmware, and retry **Flash**. If the board no longer appears, follow its documented bootloader recovery method or ask for help before trying another target. + +## Expected result after flashing + +The flasher reports completion, the board restarts as a companion, and the supported app can discover it. If any part is missing, use the recovery path before changing another setting. + +## Configure the companion + +1. Pair the node with the supported MeshCore app on your phone or computer. +2. Give it a descriptive name that does not reveal a private location. +3. Check your local community page for an override. +4. If no override is documented, apply the MeshCore Canada baseline: **USA/Canada (Recommended)** (`910.525 MHz / 62.5 kHz / SF7 / CR5`). +5. Save the settings and reconnect after the device restarts. + +The optional **Message Settings → Auto Reset Path** preference affects how the app manages changing paths. Leave it at its default unless your local testing process calls for a different setting. + +## Verify before regular use + +1. Confirm the app reconnects and shows the expected device name and radio settings. +2. Send a test message in the **Public** channel. +3. A response such as **Heard X Repeats** indicates that at least one repeater reported hearing it. A plain **Sent** result is not proof that the settings are wrong; move to a known coverage area or ask the local community to help test. + +Do not consider the setup complete until the saved settings survive a reboot and a local test succeeds. + + +## Next step + +After the companion passes the reboot and local message check, [find your community](../provinces/index.md) and keep the board-specific USB recovery method with the device record. + +## Sources and verification limits + +- [Official MeshCore web flasher](https://meshcore.io/flasher) +- [Official MeshCore source and releases](https://github.com/meshcore-dev/MeshCore) + +A firmware maintainer must attach a dated tested-board/version matrix before this guide can be marked verified. diff --git a/docs/meshcore/flash-repeater.md b/docs/meshcore/flash-repeater.md index b00d25c..4e7eac7 100644 --- a/docs/meshcore/flash-repeater.md +++ b/docs/meshcore/flash-repeater.md @@ -1,99 +1,151 @@ -# Flashing and Configuring a Repeater Node +--- +title: Flash, configure, and bench-test a repeater +description: Back up, flash by USB, configure, verify, and recover a MeshCore repeater before it leaves the bench. +audience: + - repeater-builder + - network-operator +task: flash-repeater +scope: canada-baseline +status: draft +owner: docs-firmware +last_reviewed: 2026-07-19 +review_by: 2026-10-17 +difficulty: intermediate +estimated_time: 30-60 minutes +destructive: true +requires: + - supported-repeater-board + - physical-usb-access + - data-capable-usb-cable +page_styles: + - assets/styles/devices-builds.css +--- +# Flash, Configure, and Bench-Test a Repeater -This guide will help you flash a node and configure it as a MeshCore repeater. +
---- +**Safety-flow reviewed; board/version compatibility not yet verified.** Confirm every board, bootloader, role, and firmware file against current upstream sources before acting. -## nRF52 Bootloader Update +
-*(Skip this section if you are not using an nRF52-based board)* +Prepare, flash, configure, and bench-test a MeshCore repeater before deployment. -**Important:** -Before configuring a repeater, you must update the bootloader on **nRF52 based** boards (e.g, RAK 4631, Xiao NRF52840, Heltec T114, etc). -Without this fix, a failed OTA update can brick the repeater and require physical recovery. +- **Scope:** MeshCore Canada onboarding baseline +- **Status:** Operational guide; board, firmware, and local region review required -### Steps +## Before You Change Firmware -1. Download the UF2 file (they have the 'update-' prefix) of the OTA bootloader fix for your device in the **[OTAFIX GitHub Repo](https://github.com/oltaco/Adafruit_nRF52_Bootloader_OTAFIX/releases)** +!!! danger "Back up the repeater before any erase" + **Erase Flash** can remove the repeater's identity/private key, admin access, name, location, region definitions, radio settings, and other saved configuration. A deployed identity cannot be recreated unless its private key was backed up. - Examples: +For an existing repeater, record or export: - RAK 4631 -> update-wiscore_rak4631_board_bootloader-0.9.2-OTAFIX2.2-BP1.3_nosd.uf2 +- exact board model, role, and current firmware version; +- device name, radio settings, location choice, advert settings, path-hash mode, and region configuration; +- admin/guest access information in your password manager; and +- the private key through a supported secure backup method. - Heltec T114 -> update-heltec_t114_bootloader-0.9.2-OTAFIX2.2-BP1.3_nosd.uf2 +Never post the private key or passwords in an issue, screenshot, log, or chat. If a required backup cannot be completed, stop before erasing. - Xiao NRF52840 (Used in Ikoka Stick) -> update-xiao_nrf52840_ble_sense_bootloader-0.9.2-OTAFIX2.1-BP1.2_nosd.uf2 +## Prerequisites and preflight -2. Connect your repeater to your computer via USB. -3. Double-click the button beside the USB port on the RAK board or the reset button on other boards. - - The green LED should turn on, indicating DFU mode (On the RAK specically this will occour). -4. A new **USB drive** should appear on your computer. -5. Drag the `.uf2` file into the drive. -6. The copy will appear to fail, and the board will reboot — **this is expected**. -7. Open **INFO.TXT** on the drive and confirm it reports bootloader version **0.9.2**. +- [ ] The exact board and role are confirmed. +- [ ] Existing identity and settings are backed up securely. +- [ ] A known-good data USB cable and stable power are available. +- [ ] I can regain physical USB access if setup or an update fails. +- [ ] I checked the [Mesh Directory](../provinces/index.md) and the [Repeater Configurator](../config/index.md) for the correct local region and settings. +- [ ] I will bench-test before installing the repeater at height or in a remote enclosure. ---- +## What this will change -## Flashing MeshCore Repeater Firmware - USB (Recommended Route) +An erase replaces firmware and can delete identity and settings. The setup also writes radio, advert, path-hash, access, location, and region values that affect the shared network. -1. Plug the device into your computer via USB. -2. Open the **MeshCore Web Flasher**: -3. Select your device hardware. -4. Select **Repeater** as the firmware type. -5. Click **Enter DFU Mode**. -6. Click **Erase Flash**. -7. Click **Flash** to install the firmware. +## Recovery plan -**Note:** -If flashing fails after erasing, refresh the page, click **Enter DFU Mode** again, then click **Flash**. +Keep the exact board's USB recovery method, known-good data cable, backed-up identity/settings, and verified firmware file at the bench. If an erase or flash fails, do not try another board target; return the exact board to DFU/bootloader mode and retry the same verified role by USB. ---- +## nRF52 bootloader decision -## Configuring a MeshCore Repeater +Skip this section for boards that are not nRF52-based. -1. Using a Browser that supports the [required serial connection](https://developer.mozilla.org/en-US/docs/Web/API/Web_Serial_API#browser_compatibility) (e.g., Google Chrome or Microsoft Edge), open the **MeshCore Web Flasher**: - +For supported nRF52 boards, MeshCore Canada currently directs operators to the [OTAFIX releases](https://github.com/oltaco/Adafruit_nRF52_Bootloader_OTAFIX/releases) before relying on OTA recovery. Confirm the file matches the exact board; using another board's bootloader can require physical recovery. -2. Click the **Repeater Setup** button in the top right. +Examples of historical board-specific filenames include: -3. Connect to your repeater and check the **Show Advanced Settings** checkbox near the end so all fields below are visible. +- RAK4631: `update-wiscore_rak4631_board_bootloader-0.9.2-OTAFIX2.2-BP1.3_nosd.uf2` +- Heltec T114: `update-heltec_t114_bootloader-0.9.2-OTAFIX2.2-BP1.3_nosd.uf2` +- XIAO nRF52840 used in the Ikoka Stick: `update-xiao_nrf52840_ble_sense_bootloader-0.9.2-OTAFIX2.1-BP1.2_nosd.uf2` -4. Set a Location either entering Lattitude/Longitude or using the map icon. +These examples are not a substitute for checking the current release and exact board. -5. Set a descriptive repeater **name** (e.g., `Callsign_R1`, `Downtown_R1`). +1. Download the matching `update-*.uf2` from the OTAFIX release page. +2. Connect the board over USB. +3. Enter its UF2 bootloader mode. On a RAK4631 this is normally done by double-pressing the button beside USB; other boards use their documented reset method. +4. Confirm a USB drive appears and inspect `INFO.TXT` so the board identity is what you expect. +5. Copy the matching UF2 file to that drive. The drive may disconnect as the board reboots. +6. Re-enter bootloader mode and confirm `INFO.TXT` reports bootloader version `0.9.2` before continuing. -6. Set an **admin password** (required for MeshCore Remote Administration). +If the board identity or expected version does not match, stop and recover over USB before flashing MeshCore. -7. Apply the USA/Canada (Recommended) Preset: - **910.525 MHz / BW 62.5 kHz / SF7 / CR5** +## Flash by USB (recommended) -8. Set the advert intervals: - 1. **Advert Interval (minutes):** `60` - 2. **Flood Advert Interval (hours):** `24` - 3. **Flood Max:** `64` +Use the official [MeshCore Web Flasher](https://meshcore.io/flasher) in a browser with [Web Serial support](https://developer.mozilla.org/en-US/docs/Web/API/Web_Serial_API#browser_compatibility). -9. Set **Path Hash Mode** to **3-byte (2)**. +1. Connect the repeater by USB. +2. Select the exact hardware model. +3. Select **Repeater** as the role and choose the intended version for that board. +4. Click **Enter DFU Mode** and wait for the flasher to find the board. +5. Recheck the selected board and role. +6. Click **Erase Flash** and wait for a successful erase message. +7. Click **Flash** and wait for completion before disconnecting. -10. *(Optional)* Set your own info (e.g., owner name or contact). +If flashing fails after erase, do not repeatedly erase. Refresh the page, return the board to DFU mode, verify the target again, and retry **Flash**. Use the board's USB recovery process if it no longer appears. -11. Click **Save Settings**, then reboot the repeater. +## Expected result after flashing -12. Reconnect with the configuration tool and click **Send Advert**. +The flasher reports completion, the board restarts as a repeater, and the setup console can reconnect. If that state is not reached, follow the recovery plan before configuration. -If everything is working, nearby companion nodes should receive the advert. +## Configure the repeater ---- +1. In the MeshCore Web Flasher, open **Repeater Setup**. +2. Connect to the repeater and enable **Show Advanced Settings** so the required fields are visible. +3. Enter the intended location or use the map. Do not publish an exact private location unless that is appropriate for the site. +4. Set a descriptive name, such as `Callsign_R1` or `Downtown_R1`. +5. Set a unique admin password and store it securely. +6. Confirm the local community has not documented an override. Otherwise apply **USA/Canada (Recommended)** (`910.525 MHz / 62.5 kHz / SF7 / CR5`). +7. Set the current MeshCore Canada baseline advert values: + - **Advert Interval:** `60` minutes + - **Flood Advert Interval:** `24` hours + - **Flood Max:** `64` +8. Use the [Repeater Configurator](../config/index.md) to determine the region commands and path-hash mode for this site. The national onboarding baseline is 3-byte (`set path.hash.mode 2`), but a documented local override takes priority. +9. Add owner information only if it is suitable for public adverts. +10. Save the settings and reboot. -**Tip:** -After every reboot, you must **resync the repeater’s clock**. -The repeater will still route messages without a clock, but **its adverts will be ignored** by companions that have already heard an advert from it until the time is set. +## Verify and bench-test ---- +1. Reconnect after reboot and sync the repeater clock. +2. Confirm the firmware version, device name, radio settings, path-hash mode, advert settings, and region configuration match the plan. +3. Send an advert and confirm a nearby companion receives it. +4. Reboot once more, reconnect, and confirm the saved configuration remains intact. +5. Verify remote administration from the intended companion before closing an enclosure or installing the repeater remotely. -## Changing Repeater Private Key After Initial Setup (not always needed) +After every reboot, resync the repeater clock. Routing can continue without a correct clock, but stale advert timing can prevent companions from accepting a new advert. -If your region is still using 1-byte mode, you may need to change your repeater's private key after you have set it up to avoid ID conflicts. If this happens, you can do it again via USB as per the [Configuring a MeshCore Repeater](#configuring-a-meshcore-repeater) section of this page, following the **[Generating a Repeater ID](generate-repeater-id.md)** instructions to pick a new ID and key. +Do not deploy until the repeater passes this bench test and USB recovery remains practical. -*Optional*: If your repeater is on MeshCore Firmware v1.12.0 and up, you can set the private key remotely by logging in to the repeater console with a companion node that has admin access to your repeater. The steps are the same for USB and remote connections. If configuring remotely, make sure you have good signal to your repeater. +## Legacy identity changes ---- +Most operators should not change a repeater identity after setup. A region that still coordinates 1-byte IDs may direct an operator to do so. Follow [Generating a Repeater ID](generate-repeater-id.md) only when the local region operator confirms it is required, and keep the old private key as the rollback path. + + +## Next step + +Keep the repeater on the bench until every verification passes. Then review the [mounting plan](../hardware/repeater-mounting-options.md) and retain physical USB access. + +## Sources and verification limits + +- [Official MeshCore web flasher](https://meshcore.io/flasher) +- [Official MeshCore source and releases](https://github.com/meshcore-dev/MeshCore) +- [OTAFIX releases referenced by the community](https://github.com/oltaco/Adafruit_nRF52_Bootloader_OTAFIX/releases) + +The listed bootloader filenames are historical examples, not a tested compatibility matrix. A firmware maintainer must reproduce the exact board/version flows before this guide can be marked verified. diff --git a/docs/meshcore/flash-room-server.md b/docs/meshcore/flash-room-server.md index 847d9f6..ac477d1 100644 --- a/docs/meshcore/flash-room-server.md +++ b/docs/meshcore/flash-room-server.md @@ -1,48 +1,111 @@ +--- +title: Flash and configure a room server +description: Back up, flash, secure, verify, and recover a supported MeshCore Room Server before deployment. +audience: + - room-server-operator + - network-operator +task: flash-room-server +scope: canada-baseline +status: draft +owner: docs-firmware +last_reviewed: 2026-07-19 +review_by: 2026-10-17 +difficulty: intermediate +estimated_time: 30-45 minutes +destructive: true +requires: + - supported-room-server-board + - data-capable-usb-cable +page_styles: + - assets/styles/devices-builds.css +--- +# Flash and Configure a Room Server -← Home +
-Follow these steps to flash and configure a device as a MeshCore Room Server: +**Safety-flow reviewed; device/version compatibility not yet verified.** Confirm the exact board and current Room Server target in the official flasher before erasing. -## Configuring a Room Server -### Flashing the Firmware -1. Open the MeshCore Web Flasher: [Link](https://meshcore.io/flasher) +
-2. Select your device from the list. +Flash a supported device as a MeshCore Room Server, secure access, and verify that a companion can discover and enter it. -3. Select **Room Server**. +- **Scope:** MeshCore Canada onboarding baseline +- **Status:** Operational guide; confirm the exact board and current firmware in the flasher -4. Click **Enter DFU Mode**. +## Before You Erase -5. Click **Erase Flash** and wait for it to complete. +!!! danger "Erase Flash deletes stored room-server data" + Erasing can remove the device identity/private key, room data, guest and admin access, name, radio settings, and other saved configuration. Back up anything you need before continuing. -6. Select your desired version (the most recent is generally recommended). +For an existing device, record the board, firmware version, name, role, radio settings, and access configuration. Export or securely record the private key with a supported tool. Store passwords in a password manager and never place them in screenshots, issues, or chat. -7. Click **Flash**. +If room history or identity cannot be backed up and must be retained, stop before erasing. -**Note:** Sometimes after erasing, the flash step may fail. +## Prerequisites and preflight -If this happens, refresh the page, click **Enter DFU Mode** again, and then click **Flash** to retry. +- [ ] The exact board and **Room Server** role are confirmed. +- [ ] Existing identity, room data, and settings are backed up where supported. +- [ ] A known-good data USB cable and stable power are available. +- [ ] I am using a current browser with Web Serial support, such as Chrome or Edge. +- [ ] I checked the local community page for radio-setting overrides. -### Configuring the Room Server -8. After the flash is complete, click **Configure via USB**. - -9. Select your console device and click **Connect**. +## What this will change -10. Set a descriptive **name** for your Room Server. +Flashing replaces firmware and **Erase Flash** can delete identity, room data, access settings, and radio configuration. Setup writes guest/admin access, name, and local radio settings. -11. Set both a **guest password** and an **admin password**. +## Recovery plan -12. Choose your radio preset: in Ottawa use **USA/Canada (Recommended)**. +Keep the exact board's USB recovery method, backed-up identity/settings, a known-good cable, and the verified Room Server artifact available before erasing. If flashing fails, return the same board to DFU mode and retry the same verified target; do not switch board files. -13. Click **Save Settings**. +## Flash the firmware -14. Click **Reboot**. +1. Open the official [MeshCore Web Flasher](https://meshcore.io/flasher). +2. Select the exact device model. +3. Select **Room Server** and the intended version for that board. +4. Click **Enter DFU Mode** and wait for the expected device to appear. +5. Recheck the hardware and role selections. +6. Click **Erase Flash** and wait for a successful erase message. +7. Click **Flash** and wait for completion before disconnecting. -### Final Steps -15. Connect back to the device via the console. +If flashing fails after erase, leave the device connected, refresh the flasher, re-enter DFU mode, confirm the board and role, and retry **Flash**. Use the board's documented USB recovery process if it is no longer detected. -16. Click **Send Advert**. +## Expected result after flashing -17. On your companion node, it should now discover your Room Server. +The flasher reports completion, the device restarts as a Room Server, and **Configure via USB** can reconnect. Otherwise use the recovery plan before setting access details. -18. Log in from your companion node to the Room Server using the **guest password**. +## Configure the Room Server + +1. After flashing, click **Configure via USB**. +2. Select the room server's serial device and connect. +3. Set a descriptive name that does not expose a private location. +4. Set separate, unique guest and admin passwords and store them securely. + - The guest password is shared with people who should enter the room. + - The admin password controls management access and should not be shared as the guest password. +5. Check the local community page for an override. If none is documented, choose **USA/Canada (Recommended)** (`910.525 MHz / 62.5 kHz / SF7 / CR5`). +6. Save settings and reboot. + +## Verify success + +1. Reconnect to the console after reboot and confirm the name, role, and radio settings. +2. Send an advert. +3. Confirm a companion discovers the Room Server. +4. Log in from the companion with the guest password. +5. Confirm the intended room behavior, then reboot once more and verify the settings persist. + +Do not deploy the server remotely until USB recovery, admin access, discovery, and guest access all work on the bench. + + +## Recovery and undo + +If discovery, guest access, admin access, or persistence fails, keep the server local and reachable by USB. Restore the backed-up identity/settings where supported or reflash the exact Room Server target by USB, then repeat verification. Do not deploy a server whose access path or recovery is uncertain. + +## Next step + +After the server survives reboot and access checks, [find the local community](../provinces/index.md) and document who maintains the room and its recovery record. + +## Sources and verification limits + +- [Official MeshCore web flasher](https://meshcore.io/flasher) +- [Official MeshCore source and releases](https://github.com/meshcore-dev/MeshCore) + +A firmware maintainer must attach a dated tested-board/version matrix before this guide can be marked verified. diff --git a/docs/meshcore/general-faq.md b/docs/meshcore/general-faq.md index f2f4dbd..f221431 100644 --- a/docs/meshcore/general-faq.md +++ b/docs/meshcore/general-faq.md @@ -1,75 +1,125 @@ -# MeshCore FAQ +--- +title: MeshCore questions and answers +description: Find short answers about Canadian settings, hardware, range, joining a mesh, and observer problems. +audience: + - newcomer + - meshcore-user + - mesh-operator +task: answer-meshcore-question +scope: canada-baseline +status: verified +owner: docs-ux +last_reviewed: 2026-07-19 +review_by: 2026-10-19 +evidence: + - Existing MeshCore Canada FAQ + - Current configurator and setup journeys +difficulty: beginner +estimated_time: 2-10 minutes +destructive: false +--- -## General +# MeshCore questions and answers -??? question "What frequencies does MeshCore use in Canada?" - MeshCore Canada communities should start with the **USA/Canada (Recommended)** preset. +Search this page with your browser's **Find** command, or choose a topic below. +Settings can differ by place, so this page links to the current source instead +of repeating values that may change. - If your app or config tool shows raw radio values instead of a named preset, use: +- [Settings and range](#settings-and-range) +- [Hardware](#hardware) +- [Joining or starting a mesh](#joining-or-starting-a-mesh) +- [Observers and troubleshooting](#observers-and-troubleshooting) - | Field | Value | - |-------|-------| - | Frequency | `910.525 MHz` | - | Bandwidth | `62.5 kHz` | - | Spreading Factor | `SF7` | - | Coding Rate | `5` | +## Settings and range - Always check your local province or community page in case a nearby mesh publishes a different setting. +### Which radio settings should I use in Canada? -??? question "What is 3-byte path hash mode?" - MeshCore adverts include compact path identifiers. MeshCore Canada recommends **3-byte** path hashes because larger repeater-backed networks have more room for unique identifiers than with the legacy 1-byte setting. +Use the [repeater configurator](../config/index.md) for a repeater. It returns +the current settings for the location you select. - In the MeshCore CLI, use: +For any role, first check the [community directory](../provinces/index.md). +Follow a published local override when one exists. - ```text - set path.hash.mode 2 - ``` +### What is path hash mode? -??? question "Do I need a ham radio license?" - MeshCore Canada cannot give legal advice. Most Canadian MeshCore community docs assume licence-exempt LoRa operation in the appropriate ISM band, but you are responsible for using legal frequencies, power levels, antennas, and duty cycle in your location. +It controls the size of the identifiers used in advert paths. The Canadian +region standard and configurator provide the current setting for a repeater. +Use their output instead of copying an old command from a discussion or +screenshot. - If you are operating as an amateur radio station or using non-standard equipment, check the current ISED rules and local amateur radio guidance before transmitting. +[Read the region standard](../config/standard.md). -??? question "What range should I expect?" - Range depends heavily on antenna quality, height, terrain, obstructions, noise floor, and line of sight. A handheld device indoors may only cover a neighborhood. A well-placed outdoor repeater with a clear antenna view can cover much more. +### Do I need an amateur radio licence? - For troubleshooting, compare against a nearby known-good node before assuming the firmware or MQTT path is broken. +MeshCore Canada cannot decide which authorization applies to your station. +You are responsible for legal frequency, power, antenna, and operating choices +in your location. + +Start with the [Canadian regulatory links](../resources/links.md#radio-and-regulatory-references). +If you are unsure, ask a qualified local operator before transmitting. + +### What range should I expect? + +Range depends on antenna quality and placement, height, terrain, buildings, +interference, and line of sight. Test against a nearby known-good node before +assuming the device or firmware is faulty. ## Hardware -??? question "What devices are compatible with MeshCore?" - Use devices listed by the MeshCore Flasher or by a MeshCore Canada build guide for the role you need. Compatibility varies by radio chip, flash size, board wiring, display, battery hardware, and WiFi support. +### Which devices work with MeshCore? + +Use a device listed by the official MeshCore Flasher or by a MeshCore Canada +guide for the role you need. Support depends on the board and firmware target. + +[Compare device roles](general-meshcore-roles.md), then open the hardware guide +from the matching setup path. + +### Can I reuse a device that currently runs Meshtastic? + +Some supported boards can be reflashed with MeshCore firmware. A device still +running Meshtastic firmware will not join a MeshCore network. + +Confirm the exact board in the official flasher before changing it, and back up +anything you need to keep. + +### Which board should I buy first? + +Choose the role first. For a companion, prefer a supported device with nearby +community experience. For a fixed service, reliable power, antenna placement, +and maintainability matter more than screen features. + +## Joining or starting a mesh - Standalone MQTT observer firmware targets WiFi-capable LoRa boards published by the [observer flasher setup guide](../analyzer/builds/mqtt-firmware.md). +### How do I join a nearby mesh? -??? question "Can I use my Meshtastic device with MeshCore?" - Sometimes, but it must be flashed with MeshCore firmware and supported by the MeshCore build you choose. A device running Meshtastic firmware will not join a MeshCore mesh. +1. [Find the local community](../provinces/index.md). +2. Follow its published settings or use the Canadian baseline when no override + is listed. +3. Complete the setup path for your [device role](general-meshcore-roles.md). +4. Send an advert and ask a nearby user to confirm it. - Back up any identity or configuration you care about before reflashing. Treat a first MeshCore flash as a new setup. +### How do I start a mesh where none is listed? -??? question "Which board should I buy first?" - For a first companion, choose a board or ready-made device that is listed in the official MeshCore tools and has community support near you. For a fixed repeater or observer, prioritize stable power, a good antenna path, and remote access over display features. +Begin with companions so people can test locally. Add fixed infrastructure only +after checking placement, power, local coordination, and the region standard. -## Network +When the group is ready, [add the community](../contributing.md) so others can +find its status, contact path, and any reviewed local settings. -??? question "How do I join an existing mesh network?" - 1. Find your local page in the [Mesh Directory](../provinces/index.md). - 2. Set the radio preset to **USA/Canada (Recommended)** unless the local page says otherwise. - 3. Set path hash mode to **3-byte**. - 4. Reboot the device after changing radio settings. - 5. Send an advert and check whether nearby users can see you. +## Observers and troubleshooting -??? question "How do I set up a new mesh in my area?" - Use the MeshCore Canada baseline unless you have a local reason to publish a different setting: +### Why does my observer show no packets? - ```text - set radio 910.525,62.5,7,5 - set path.hash.mode 2 - ``` +A connection to the data service does not prove the radio is hearing nearby +traffic. Check the selected radio settings, packet publishing, device activity, +and the final verification result. - Then open an update request through [Contributing](../contributing.md) so the directory can list the community, region, status, contacts, and any setting differences. +Use [Check your observer](../analyzer/verify.md), then follow +[observer troubleshooting](../analyzer/troubleshooting.md) for the symptom you +see. -??? question "Why does my observer show no packets?" - A broker connection only proves the observer reached MQTT. It may still hear no mesh traffic if the radio preset is wrong, path hash mode is wrong, packet publishing is disabled, or no nearby nodes are active. +### Where can I ask another question? - Use [Check Your Observer](../analyzer/verify.md) and [Troubleshooting](../analyzer/troubleshooting.md) to narrow the symptom. +Use [Get help](../start/get-help.md) for the shortest support route. Remove +passwords, private keys, precise private locations, and other sensitive values +before sharing screenshots or logs. diff --git a/docs/meshcore/general-howto.md b/docs/meshcore/general-howto.md index f453a5e..effe4ac 100644 --- a/docs/meshcore/general-howto.md +++ b/docs/meshcore/general-howto.md @@ -1,177 +1,186 @@ -# MeshCore How To - -The MeshCore How-To section provides simple, step-by-step guides for the most common tasks you’ll perform on the Ottawa MeshCore network. -Whether you’re sharing your contact URL, importing someone else’s, or tracing multi-hop routes across the mesh, each walkthrough is designed to be clear, visual, and easy to follow. - -These instructions use the MeshCore mobile app and apply to both new and experienced users. More guides will be added as the platform grows and as the community discovers useful workflows. - -## How to Share Your Contact URL - -1. Open the MeshCore app and connect to your node. - -2. Tap the **Signal** icon. - - ![](images/MeshCore_GetContactID1.png){ width="300" } - -3. Tap **Advert → To Clipboard**. - - ![](images/MeshCore_GetContactID2.png){ width="300" } - -4. Paste your contact URL anywhere you want to share it. - +--- +title: Use common MeshCore app tools +description: Share a contact, import a contact, trace a path, and inspect heard repeats in the MeshCore app. +audience: + - companion-user + - meshcore-user +task: use-meshcore-app-tools +scope: upstream-meshcore +status: draft +owner: docs-app +last_reviewed: 2026-07-19 +review_by: 2026-10-19 +difficulty: beginner +estimated_time: 5-15 minutes +destructive: false --- -## How to Import a Contact URL +# Use common MeshCore app tools -1. Open the app and connect to your node. +Choose the task you need. These screenshots show one MeshCore mobile app +layout; names and positions may move in another app version. -2. Tap the **three dots**. +- [Share your contact link](#share-your-contact-link) +- [Import a contact link](#import-a-contact-link) +- [Trace a path](#trace-a-path) +- [Check heard repeats](#check-heard-repeats) - ![](images/MeshCore_AddContactMan1.png){ width="300" } +
-3. Tap **Add Contact**. +**Before you start** - ![](images/MeshCore_AddContactMan2.png){ width="300" } +- Connect the app to your companion. +- Confirm the companion can send and receive locally. +- Do not post private keys, passwords, or precise private locations in a shared + screenshot. -4. Tap **Import from Clipboard Link**. +
- ![](images/MeshCore_AddContactMan3.png){ width="300" } +## Share your contact link -5. After a few seconds you will see: +Use this when another person needs to add you as a contact. - **"Success - contact has been imported"** +1. Open the MeshCore app and connect to your companion. - ![](images/MeshCore_AddContactMan5.png){ width="300" } +2. Open the **Signal** screen. -6. A second popup appears: + ![Signal control in the MeshCore app](images/MeshCore_GetContactID1.png){ loading=lazy width="300" } - **"New Contact Discovered \"** +3. Select **Advert**, then **To Clipboard**. -7. The contact is now added. + ![Advert menu with the To Clipboard action](images/MeshCore_GetContactID2.png){ loading=lazy width="300" } ---- +4. Paste the contact link into the conversation where you intend to share it. -## How to Trace Route to a Node (1 Hop) +
-1. Open the app and connect. +**Expected result:** the clipboard contains a contact link that another +MeshCore user can import. -2. Tap the **three dots**. +
- ![](images/MeshCore_TraceRoute1.png){ width="300" } +## Import a contact link -3. Tap **Tools**. +Use this after another person sends you their MeshCore contact link. - ![](images/MeshCore_TraceRoute2.png){ width="300" } +1. Copy only the contact link, then connect the app to your companion. -4. Tap **Trace Path - Manual**. +2. Open the **three-dot** menu. - ![](images/MeshCore_TraceRoute3.png){ width="300" } + ![Three-dot menu in the MeshCore app](images/MeshCore_AddContactMan1.png){ loading=lazy width="300" } -5. Tap the **plus button**. +3. Select **Add Contact**. - ![](images/MeshCore_TraceRoute4.png){ width="300" } + ![Add Contact action](images/MeshCore_AddContactMan2.png){ loading=lazy width="300" } -6. Select a repeater and confirm. +4. Select **Import from Clipboard Link**. - ![](images/MeshCore_TraceRoute1Hop1.png){ width="300" } + ![Import from Clipboard Link action](images/MeshCore_AddContactMan3.png){ loading=lazy width="300" } -7. You will see one repeater ID; this indicates a **1-hop trace**. +5. Wait for the success message and the new-contact notice. - ![](images/MeshCore_TraceRoute1Hop2.png){ width="300" } + ![Successful contact import message](images/MeshCore_AddContactMan5.png){ loading=lazy width="300" } -8. Tap **Trace Path**. +
---- +**Expected result:** the named contact appears in your contact list. -## How to Trace Route to a Node (2+ Hops) +
-1. Open the app and connect. +If the import fails, copy the original link again without extra words or +punctuation. Ask the sender for a new link if it still fails. -2. Tap the **three dots**. +## Trace a path - ![](./images/MeshCore_TraceRoute1.png){ width="300" } +A manual trace tests the repeater sequence you select. It does not guarantee +that later messages will use the same path. -3. Tap **Tools**. +### Open the trace tool - ![](./images/MeshCore_TraceRoute2.png){ width="300" } +1. Connect the app to your companion. +2. Open the **three-dot** menu. -4. Tap **Trace Path - Manual**. + ![Three-dot menu in the MeshCore app](images/MeshCore_TraceRoute1.png){ loading=lazy width="300" } - ![](./images/MeshCore_TraceRoute3.png){ width="300" } +3. Select **Tools**. -5. Tap the **plus button**. + ![Tools action in the MeshCore app](images/MeshCore_TraceRoute2.png){ loading=lazy width="300" } - ![](./images/MeshCore_TraceRoute4.png){ width="300" } +4. Select **Trace Path - Manual**. -6. Select repeaters **in order**: - - Choose the **forward path** - - Confirm - - Re-open the add menu and choose the **return path** - - Or manually enter IDs: - - Example: `d3, f3, d3` + ![Manual trace-path action](images/MeshCore_TraceRoute3.png){ loading=lazy width="300" } - ![](./images/MeshCore_TraceRoute2Hop1.png){ width="300" } +5. Select the **plus** button to add a repeater. -7. Confirm both forward and return paths, then tap **Trace**. + ![Add-repeater button in the manual trace tool](images/MeshCore_TraceRoute4.png){ loading=lazy width="300" } - ![](./images/MeshCore_TraceRoute2Hop2.png){ width="300" } +### Trace one hop -8. View the results. +1. Add one repeater and confirm it. - ![](./images/MeshCore_TraceRoute2Hop3.png){ width="300" } + ![One repeater selected for a trace](images/MeshCore_TraceRoute1Hop1.png){ loading=lazy width="300" } ---- +2. Confirm that one repeater identifier is shown. -## Heard Repeats + ![One-hop path ready to trace](images/MeshCore_TraceRoute1Hop2.png){ loading=lazy width="300" } -In the MeshCore app you can generally see if a repeater heard your message you sent in a channel. +3. Select **Trace Path**. -When you send a message out, the packet travels through the airwaves, hits a nearby repeater and it then repeats your packet out. If your companion is within receiving range and receives the repeated packet, it will be counted as a "heard repeat". There are cases where your companion could miss the heard repeat. Examples of this are: +### Trace more than one hop -- Your packet makes it to the repeater, but the repeated packet dosn't make it back to your companion. +1. Add the repeaters in the forward order required by the path. +2. Add the return sequence shown by your local path plan. -- If you're using a 1w companion, and the repeater that hears you is a 0.3w. You can send further than it - so while your packet may make it to the repeater, when it repeats your packet it's possible you're too far away to receive it. + ![Multiple repeaters selected for a trace](images/MeshCore_TraceRoute2Hop1.png){ loading=lazy width="300" } -## How to Check Heard Repeats +3. Review the complete sequence, then select **Trace**. -1. Send a message in the public channel. + ![Multi-hop path ready to trace](images/MeshCore_TraceRoute2Hop2.png){ loading=lazy width="300" } -2. When the app shows **Heard X repeats** under your message, press and hold the message. +4. Read the result. - ![](images/MeshCore_HeardRepeats_Step1.png){ width="300" } + ![Multi-hop trace result](images/MeshCore_TraceRoute2Hop3.png){ loading=lazy width="300" } -3. Tap **Heard Repeats**. +If you do not know the forward and return sequence, ask the local community +instead of guessing repeater identifiers. - ![](images/MeshCore_HeardRepeats_Step2.png){ width="300" } +## Check heard repeats -4. You’ll see a list of every repeater your companion heard repeating that packet. +A heard repeat means your companion received a repeated copy of a packet it +sent. A missing repeat does not by itself show where the packet failed: the +repeater may not have heard the packet, or your companion may not have heard +the repeated copy. - ![](images/MeshCore_HeardRepeats_Step3.png){ width="300" } +1. Send a message in the intended channel. +2. When **Heard _n_ repeats** appears below the message, press and hold the + message. -5. Tap a repeater in the list to view the path the repeated packet took to get back to you. See Below for the types of repeats you will see. + ![Message showing a heard-repeat count](images/MeshCore_HeardRepeats_Step1.png){ loading=lazy width="300" } -**Notes** +3. Select **Heard Repeats**. -- A repeater may show up as a **direct hop** if it is close to you. -- You may also see a **distant repeater** listed. This happens when that repeater hears your packet shortly after another one and your companion hears both repeats. -- Your companion must hear the repeater *directly* for it to appear in this list. + ![Heard Repeats action](images/MeshCore_HeardRepeats_Step2.png){ loading=lazy width="300" } ---- +4. Review the repeaters your companion heard for that packet. -### Direct Heard Packet + ![List of repeaters heard by the companion](images/MeshCore_HeardRepeats_Step3.png){ loading=lazy width="300" } -Below is an example of what a direct heard packet looks like: +5. Select a repeater to inspect the reported path. - ![Diagram Explanation](images/MeshCore_HeardRepeats_Direct.png){ width="300" } +### Example: direct repeat - ![Companion View](images/MeshCore_HeardRepeats_Step4_1Repeat.png){ width="300" } +![Diagram of a direct heard repeat](images/MeshCore_HeardRepeats_Direct.png){ loading=lazy width="300" } ---- +![App view of a direct heard repeat](images/MeshCore_HeardRepeats_Step4_1Repeat.png){ loading=lazy width="300" } + +### Example: multi-hop repeat -### Multi Hop Heard Packet +![Diagram of a multi-hop heard repeat](images/MeshCore_HeardRepeats_MultiHop.png){ loading=lazy width="300" } -Below is an example of what it looks like when one repeater hears your message, repeats it, and then your companion hears a second repeater’s repeat of that same packet: +![App view of a multi-hop heard repeat](images/MeshCore_HeardRepeats_Step4_2Repeat.png){ loading=lazy width="300" } - ![Diagram Explanation](images/MeshCore_HeardRepeats_MultiHop.png){ width="300" } +## Next step - ![Companion View](images/MeshCore_HeardRepeats_Step4_2Repeat.png){ width="300" } +If a result is missing or unclear, compare one known-good nearby node and then +use [Get help](../start/get-help.md). Include the task and step where you +stopped, but remove sensitive values before sharing a screenshot. diff --git a/docs/meshcore/general-meshcore-roles.md b/docs/meshcore/general-meshcore-roles.md index b053b89..b302221 100644 --- a/docs/meshcore/general-meshcore-roles.md +++ b/docs/meshcore/general-meshcore-roles.md @@ -1,45 +1,100 @@ -# MeshCore Device Roles +--- +title: Compare MeshCore device roles +description: Compare companions, repeaters, room servers, and observers before choosing a setup path. +audience: + - newcomer + - first-time-user + - mesh-operator +task: compare-meshcore-roles +scope: upstream-meshcore +status: verified +owner: docs-ux +last_reviewed: 2026-07-19 +review_by: 2027-01-19 +evidence: + - Existing MeshCore Canada role guidance + - Current in-repository setup journeys +difficulty: beginner +estimated_time: 3-5 minutes +destructive: false +--- -MeshCore defines several distinct roles within the network, and each role requires its own specific firmware. -A single piece of hardware can serve as a companion node, a repeater, or a room server depending on what firmware is flashed. -This section explains what each role does and how they work together within the MeshCore network. +# Compare MeshCore device roles -## Companion Nodes +Choose a role by the job the device will do. Firmware determines the role, so +the same supported board may have different jobs after it is flashed. -A **companion node** is a small personal device (handheld or portable) that lets a user connect to the mesh. +## At a glance -- Runs on battery or USB power -- Usually pairs with a smartphone over Bluetooth for messaging -- Standalone options like the **T-Deck** include a screen and keyboard, but we don’t recommend them for beginners since the firmware is still rough -- Companion nodes do **not** route packets -- They can communicate directly with each other -- **Only repeaters** perform routing across the MeshCore network +| Role | Main job | Usually mobile? | Relays mesh traffic? | Phone or computer | Typical power | Start | +|---|---|---:|---:|---|---|---| +| Companion | Send and receive messages | Yes | No | Usually uses an app; some devices are standalone | Battery or USB | [Set up a companion](../start/companion.md) | +| Repeater | Extend network coverage | No | Yes | Used for setup and maintenance | Continuous mains or solar | [Set up a repeater](../start/repeater.md) | +| Room server | Keep a shared room available | No | Not its main job | Used for setup; companions connect to the room | Continuous power | [Set up a room server](../start/room-server.md) | +| Observer | Send network data to public tools | No | No | Depends on the observer method | Continuous power | [Set up an observer](../start/observer.md) | -**→ See [Recommended Companions](../hardware/recommended-companions.md)** +
---- +**New to MeshCore?** Start with a companion. The other roles operate a service +for a mesh and are easier to set up after you can send and receive messages. -## Repeaters +
-A **repeater** is a fixed installation, typically mounted at elevation (rooftop, tower, mast), that extends range and links mesh segments. +## Companion -- Runs continuously on mains or solar power -- Most Ottawa repeaters operate on solar -- In MeshCore, repeaters form the stable **backbone** of the network -- They are the **only devices** that perform packet routing +A companion is the personal device used for messaging. -**→ See [Recommended Repeaters](../hardware/recommended-repeaters.md)** +- It usually connects to a phone, tablet, or computer. +- Some supported devices include their own screen and controls. +- It can communicate with nearby nodes but does not relay traffic for the mesh. ---- +[Start the companion path](../start/companion.md){ .md-button .md-button--primary } + +## Repeater + +A repeater relays packets and extends coverage. + +- It is normally fixed in place and runs continuously. +- Antenna placement, reliable power, and safe mounting matter more than a + display. +- Coordinate with the local community before adding or changing a repeater. + +[Start the repeater path](../start/repeater.md){ .md-button .md-button--primary } + +## Room server + +A room server keeps recent room messages available for companions that connect +to it. + +- It is normally fixed and continuously powered. +- It hosts a shared room; it is not a replacement for a repeater. +- Plan who will administer the room and keep its access details private. + +[Start the room-server path](../start/room-server.md){ .md-button .md-button--primary } + +## Observer + +An observer listens for MeshCore traffic and sends network data to public tools. + +- It does not relay mesh traffic. +- It can be a radio with observer firmware or a supported host-and-radio setup. +- Choose the observer method before buying hardware because requirements differ. + +[Start the observer path](../start/observer.md){ .md-button .md-button--primary } + +## Still unsure? + +Choose the result you want: + +- personal messaging: **companion**; +- wider local coverage: **repeater**; +- a persistent shared room: **room server**; +- data in public network tools: **observer**. -## Room Servers +Before buying or flashing, check the hardware and firmware guide linked from +the role path. Support varies by device and firmware target. -A **room server** is a device flashed with specialized firmware that functions like a persistent chat room or mini-BBS. +## Next step -- Stores the last **32 messages** sent to it -- When a companion node connects, it retrieves stored messages (similar to checking an inbox) -- While they technically can repeat, this is strongly discouraged - - Ottawa disables repeat on room servers - - Developers have discussed removing the option entirely -- Room servers are **not full repeaters** and lack many repeater features -- Best used as static message boards or shared chat nodes, **not** as repeaters \ No newline at end of file +Open the matching setup path. If none fits, [ask the community](../start/get-help.md) +before buying hardware. diff --git a/docs/meshcore/general-overview.md b/docs/meshcore/general-overview.md index 7dae50d..0ee2488 100644 --- a/docs/meshcore/general-overview.md +++ b/docs/meshcore/general-overview.md @@ -1,4 +1,28 @@ -# MeshCore Overview +--- +title: MeshCore roles and Canadian onboarding +description: Understand MeshCore device roles, separate Canadian baseline guidance from local practice, and choose a safe next step. +audience: + - newcomer + - meshcore-operator +task: understand-meshcore-roles +scope: canada-baseline +status: draft +owner: docs-ux +last_reviewed: 2026-07-19 +review_by: 2026-10-17 +difficulty: beginner +estimated_time: 5-10 minutes +destructive: false +page_styles: + - assets/styles/devices-builds.css +--- +# MeshCore Roles and Canadian Onboarding + +
+ +**Orientation page.** MeshCore Canada explains community onboarding and Canadian practices; the official MeshCore project remains authoritative for firmware, protocol, releases, and supported targets. + +
!!! important "MeshCore project split, use the official links" Due to recent events in the MeshCore development team, the project has split. To stay on the official track, please only use: @@ -9,50 +33,47 @@ Read more about the split: [The Split, blog.meshcore.io](https://blog.meshcore.io/2026/04/23/the-split). -MeshCore is a **repeater-driven long-range mesh network** built on top of LoRa radios. This section of the wiki explains the basics of how MeshCore works in the Ottawa region, how the different device roles fit together, and walks through the most common tasks you will perform as a new user. +MeshCore is a repeater-driven mesh network built on LoRa radios. A device's firmware determines whether it acts as a companion, repeater, room server, or another supported role. -If you are brand new to MeshCore, start here and work your way through the pages below in order. +- **Scope:** MeshCore basics and MeshCore Canada onboarding +- **Status:** Overview; use local community pages for local operating practices ---- +MeshCore Canada documents the Canadian network and community practices. It does not replace the official MeshCore firmware, protocol, or upstream documentation. + +## Choose a Role + +- A **companion** connects a person and their app to the mesh. +- A **repeater** forwards mesh traffic and supports network coverage. +- A **room server** hosts persistent room messages for its users. -## What's in This Section +Read [MeshCore Roles](general-meshcore-roles.md) before choosing firmware. -### [MeshCore Roles](general-meshcore-roles.md) -Explains the three device roles (**Companion Nodes**, **Repeaters**, and **Room Servers**), what each one does, and how they interact on the mesh. A single piece of hardware can play any of these roles depending on the firmware you flash, so understanding this section helps you pick the right firmware for your device. +## Canada and Local Settings -### [MeshCore FAQ](general-faq.md) -Covers the most common questions about how MeshCore behaves on the Ottawa network, including: +MeshCore Canada publishes a national onboarding baseline. A province, territory, or local community may document an override for its network. Check the [Mesh Directory](../provinces/index.md) before configuring a device, and follow a documented local override when one exists. -- How adverts work and the Ottawa advert schedule -- Routing behaviour and repeater neighbours -- The public channel and how "Heard Repeats" is displayed in the app +Ottawa-specific schedules and field experience are local practice, not proof of a Canada-wide standard. -### [MeshCore How-To](general-howto.md) -Step-by-step walkthroughs for day-to-day tasks in the MeshCore mobile app, such as sharing your contact URL, importing contacts, and tracing routes. Each guide is visual and suitable for both new and experienced users. +## Start Here -### [Repeater Configurator](../config/index.md) -An interactive tool that finds the right Canadian region for a repeater and produces the exact commands to configure it — radio settings, region path, and verification steps. Also includes the [region map](../config/map.md) and the [region standard](../config/standard.md). +1. Read [MeshCore Roles](general-meshcore-roles.md). +2. Choose a [recommended companion](../hardware/recommended-companions.md). +3. Follow [Flashing a Companion](flash-companion.md). +4. Find your local community in the [Mesh Directory](../provinces/index.md). +5. Use the [MeshCore How-To](general-howto.md) guides to share contacts and send messages. -### Firmware Guides -Everything you need to get firmware onto a device: +## Operator and Reference Guides -- [Flashing a Companion](flash-companion.md) +- [MeshCore FAQ](general-faq.md) - [Flashing a Repeater](flash-repeater.md) -- [Updating a Repeater (OTA)](update-repeater-ota.md) -- [Generating a Repeater ID](generate-repeater-id.md) - [Flashing a Room Server](flash-room-server.md) -- [RAK4631 Custom Display Firmware](firmware-rak-custom-display.md) -- [Heltec V3 Wi-Fi Firmware](firmware-heltec-v3-wifi.md) - ---- +- [Repeater Configurator](../config/index.md) +- [Region map](../config/map.md) +- [Region standard](../config/standard.md) -## New to MeshCore? +Experimental and legacy firmware pages are retained for review but are not part of this beginner path. -If you are just getting started, we recommend this path: -1. Read **[MeshCore Roles](general-meshcore-roles.md)** to understand what a companion node, repeater, and room server each do. -2. Pick a **[Recommended Companion](../hardware/recommended-companions.md)** and flash it using the **[Flashing a Companion](flash-companion.md)** guide. -3. Skim the **[MeshCore FAQ](general-faq.md)** so you know what to expect from the network. -4. Use the **[MeshCore How-To](general-howto.md)** walkthroughs to share contacts and start messaging. +## Next step -When you are ready to contribute to the network backbone, move on to **[Flashing a Repeater](flash-repeater.md)** and the **[Hardware](../hardware/recommended-repeaters.md)** section. +If you are new, [choose your role](../start/choose-a-goal.md). If you already know the role, continue to the matching flashing guide and verify success before using advanced tools. diff --git a/docs/meshcore/generate-repeater-id.md b/docs/meshcore/generate-repeater-id.md index 39e4235..213ed5c 100644 --- a/docs/meshcore/generate-repeater-id.md +++ b/docs/meshcore/generate-repeater-id.md @@ -1,15 +1,81 @@ -## Generating a Repeater ID +--- +title: Change a repeater ID for a legacy 1-byte region +description: Coordinate, back up, change, verify, and restore a repeater identity only when a legacy region requires it. +audience: + - legacy-region-operator + - repeater-maintainer +task: change-legacy-repeater-id +scope: legacy +status: legacy +owner: docs-firmware +last_reviewed: 2026-07-19 +review_by: 2026-10-17 +difficulty: advanced +estimated_time: 15-30 minutes +destructive: true +requires: + - local-region-approval + - trusted-serial-connection + - secure-key-storage +page_styles: + - assets/styles/devices-builds.css +--- +# Change a Repeater ID for a Legacy 1-Byte Region -Regions that are using 1-byte path sizes usually generate Repeater IDs manually to avoid accidentally reusing an ID that’s already in service. Follow the steps below to pick another ID and program the matching private key onto your repeater. +
-!!! note "Where this applies" - Regions that have transitioned to multi-byte path sizes, will not need to worry about overlapping repeater id's.These instructions are left here for those cases where users want to generate a new repeater ID. +**Legacy exception.** Do not use this in the Canada 3-byte baseline. Proceed only when the local region operator confirms that the repeater remains in 1-byte mode and coordinates the new ID. -1. Open the **[MeshCore Key Generator](https://gessaman.com/mc-keygen/)**. -2. Type the unused ID(2-6 charectors) into the input field and click **Generate Key**. -3. Copy the **Private Key** value. -4. On the repeater console, run (replace `` with the value you copied): - `set prv.key ` -5. Reboot the repeater. +
-After reboot, the repeater will use that private key, and its public key will correspond to the ID you selected. +!!! warning "Legacy exception — do not use by default" + MeshCore Canada's onboarding baseline uses 3-byte path hashes. Manually coordinating repeater IDs is only for a local region that still operates in 1-byte mode and has asked you to use this process. + +- **Scope:** Local 1-byte compatibility only +- **Status:** Legacy procedure; local region approval required + +## What this will change + +Changing the private key changes the node identity. It can break existing administration, contact, and tracking relationships. The old key is the only identity rollback path. + +## Prerequisites and backup + +1. Confirm with the local region operator that the repeater must remain in 1-byte mode and that a new ID is required. +2. Connect to the repeater and record its current public key with `get public.key`. +3. Over a trusted serial connection, back up the current private key with `get prv.key` and store it as a secret in a secure location. +4. Confirm the proposed 2–6 character ID is unused in the local region's coordination record. + +Never post or transmit either private key through an issue, screenshot, public chat, or log. The saved old private key is the rollback path. + +## Generate and apply the key + +1. Open the [MeshCore Key Generator](https://gessaman.com/mc-keygen/). +2. Enter the locally approved, unused 2–6 character ID and select **Generate Key**. +3. Confirm the generated private key is a 64-character hexadecimal value. +4. Copy it directly to the trusted repeater console without saving it in an ordinary note or chat. +5. Run, replacing the placeholder with the generated value: + + ```text + set prv.key + ``` + +6. Reboot the repeater. + +## Verify and restore + +After reboot, run `get public.key` and confirm its prefix matches the locally approved ID. Recheck admin access, radio settings, region configuration, adverts, and local routing before returning the repeater to service. + +If verification fails, restore the backed-up old private key with `set prv.key `, reboot, and confirm the original public key and access are restored. + + +## Recovery + +If admin access, the public-key prefix, adverts, region configuration, or routing is wrong, keep the repeater on the bench. Restore the backed-up old private key over the trusted serial connection, reboot, and confirm the complete former state before returning it to service. + +## Next step + +Record the locally approved ID, operator approval, old/new public-key prefixes, verification, and rollback location without recording either private key in the public maintenance record. + +## Verification limits + +This legacy process has no attached current firmware/version test matrix. A firmware and regional operator review is required before it can be marked verified. diff --git a/docs/meshcore/update-repeater-ota.md b/docs/meshcore/update-repeater-ota.md index 130c659..b0cc959 100644 --- a/docs/meshcore/update-repeater-ota.md +++ b/docs/meshcore/update-repeater-ota.md @@ -1,93 +1,134 @@ -!!! danger "Bootloader Prerequisite" - Please only proceed with this if you have updated your bootloader firmware using the **OTA fix firmware** mentioned in the [Flashing a Repeater](flash-repeater.md#nrf52-bootloader-update) instructions. +--- +title: Update a repeater or room server over the air +description: Decide whether OTA is safe, prepare physical recovery, update an nRF52 device, and verify every retained setting. +audience: + - advanced-repeater-operator + - room-server-operator +task: update-repeater-ota +scope: ottawa-field-practice +status: experimental +owner: docs-firmware +last_reviewed: 2026-07-19 +review_by: 2026-10-17 +difficulty: advanced +estimated_time: 30-60 minutes +destructive: true +requires: + - supported-nrf52-device + - physical-usb-recovery + - verified-firmware-zip +page_styles: + - assets/styles/devices-builds.css +--- +# Update a Repeater or Room Server over the Air -!!! danger "Android Only - iOS Not Recommended" - These instructions are intended for **Android only**. iOS is technically supported, however we have experienced several failed OTA upgrades when using iOS. Until that track record changes, we do not recommend using an iOS device for OTA updates. +
---- +**Advanced Ottawa field practice.** USB remains the primary update method. Exact board, bootloader, current version, target artifact, phone/app behaviour, and recovery have not been reproduced as a current matrix on this page. -## Flashing MeshCore Repeater Firmware - Over-the-air (OTA) - Non Recommended Route +
-**Note:** This section only applies to NRF-based boards (e.g., RAK4630, Heltec T114, XIAO NRF52). Please read the warning below since we highly recommend you flash firmware using USB instead. +OTA updating is an advanced fallback for supported nRF52 devices. USB flashing remains the recommended method. -!!! warning "OTA Risks" - Although it is possible to flash a repeater's firmware OTA, there is a high risk of a flash failing (even if the app says there are no issues) which will require a USB re-flash. We have experienced an OTA failure during Winter that requires to wait until the Spring to get physical access to the repeater. Proceed at your own risk! +- **Scope:** MeshCore Canada field practice +- **Status:** Advanced and high risk; upstream support and exact board/version must be confirmed -If you are OK with these risks, you can follow the [official instructions on MeshCore's Blog](https://blog.meshcore.io/2026/04/02/nrf-ota-update). The steps are summarized below for convenience. +!!! danger "Bootloader prerequisite" + Proceed only after confirming that the exact board has the OTAFIX bootloader described in [Flashing a Repeater](flash-repeater.md#nrf52-bootloader-decision). A failed OTA update can require physical USB recovery. ---- +!!! warning "Android is the MeshCore Canada field-tested path" + The community has observed failed OTA attempts from iOS and currently recommends Android for this workflow. This is a local risk-control recommendation, not a claim that upstream iOS support does not exist. -### 1. Download the firmware `.zip` +## Prerequisites: decide whether to stop -1. Open the **[MeshCore Web Flasher](https://meshcore.io/flasher)** and find your device. -2. Select the **Repeater** or **Room Server** role, then choose the latest version. -3. In the bottom-right, click the **Download** button and select the `.zip` file. +Use USB instead if any answer below is **no**: -Alternatively, go to the **[MeshCore GitHub Releases page](https://github.com/meshcore-dev/MeshCore/releases)** and download the `.zip` artifact for your board (for example, the T114 Repeater). +- [ ] The device is an OTA-supported nRF52 board such as a RAK4631, Heltec T114, or XIAO nRF52840. +- [ ] The board identity, installed bootloader, current firmware, and intended firmware file are known. +- [ ] The private key, settings, region configuration, and access details are backed up securely. +- [ ] Stable power and a reliable radio-management path are available for the full update. +- [ ] Someone can reach the device with a USB cable if OTA fails. +- [ ] The downloaded ZIP names the exact board and role. ---- +!!! danger "No physical recovery means no OTA" + Do not start an OTA update on a roof, tower, winter site, or other inaccessible installation when a failed update cannot be recovered promptly by USB. -### 2. Log in to the repeater or room server +## What this will change -1. Using the MeshCore mobile app, log in to your device with the admin password. -2. Switch to the **Command Line** tab and enter: +OTA places the device into firmware-update mode and replaces its firmware over Bluetooth. A failed transfer can leave the device needing physical USB recovery, while a wrong artifact can target the wrong board or role. - ``` +## Recovery plan before starting + +Keep the correct USB firmware and a data cable ready. Record the current version and settings. If the device remains in DFU mode after a failed attempt, scan again for a generic DFU name and retry the same verified ZIP. If it does not recover over the air, stop and reflash the exact board/role by USB using the backup. + +## 1. Download the Exact Firmware ZIP + +1. Open the [MeshCore Web Flasher](https://meshcore.io/flasher). +2. Select the exact device and **Repeater** or **Room Server** role. +3. Choose the specific version approved for this update; do not rely on a generic “latest” label. +4. Use the download control to save the `.zip` artifact. + +Alternatively, use the [official MeshCore releases](https://github.com/meshcore-dev/MeshCore/releases) and verify the artifact is for the exact board and role. + +## 2. Put the Device in OTA Mode + +1. In the MeshCore mobile app, log in with the admin password. +2. Open **Command Line** and run: + + ```text start ota ``` -3. You should see a reply similar to: +3. Continue only after a reply similar to `OK - mac: FF:AA:BB ...` confirms OTA mode. If there is no confirmation, stop and keep the device on its current firmware. - ``` - OK - mac: FF:AA:BB ... - ``` +You may also issue `start ota` from a standalone management device that supports the repeater command line. -**Tip:** You can also use a standalone device such as a T-Deck running the Ripple firmware to issue the `start ota` command. +## 3. Transfer the Update ---- +Install Nordic's **nRF Device Firmware Update** app from the official app store for the phone. -### 3. Connect with your phone +For the MeshCore Canada Android-tested path, use: -If it isn't already installed, install the Nordic **nRF Device Firmware Update** app from **Google Play** or the **App Store**. +- **Packet receipts notification:** on +- **Number of packets:** `8` +- **Request high MTU:** off +- **Disable resume:** on +- **Prepare object delay:** `0 ms` +- **Force scanning:** on -#### 3.1 DFU App Settings +Then: -In the app, tap the **Settings** icon and apply the following recommended settings: +1. Select the verified `.zip` file. +2. Select the expected device from the scan list. +3. Start the update and keep the phone, device, and power stable until the app reports completion. -- **Packet receipts notification:** ON -- **Number of packets:** 8 -- **Request high MTU** (Android only): OFF -- **Disable resume:** ON -- **Prepare object delay:** 0 ms -- **Force scanning:** ON +If the app reports failure but a generic name such as `AdaDFU` or `RAK4631_DFU` appears, select that device and retry the same verified ZIP once. Do not switch to another board's file. -#### 3.2 Start the Update +## Expected result -1. In the app, select the `.zip` file you downloaded. -2. Select your device from the scan list. -3. Press **Start** and wait for the update to complete. +The DFU app reports completion, the device restarts, remote administration reconnects, and the device reports the intended firmware version. Anything less is a failed or incomplete update. ---- +## 4. Verify the update -### 4. Finishing Up +1. Log out and reconnect after the device restarts. +2. Run `ver` and confirm the exact intended firmware version. +3. Run `clock`; if needed, run `clock sync` from a supported remote management device. +4. Confirm the radio, path-hash, region, advert, access, and role settings still match the saved configuration. +5. Send an advert and complete a local message-routing test. -1. Once the update completes, log out and log back in (either with the app or a standalone device). -2. Verify the clock is correct with the `clock` command. If it is incorrect, issue: +The update is not complete until the device reconnects, reports the intended version, retains its configuration, and passes the local test. - ``` - clock sync - ``` +For upstream context, see the [official MeshCore OTA instructions](https://blog.meshcore.io/2026/04/02/nrf-ota-update). -3. Run the `ver` command to confirm the firmware version has been updated. ---- +## Recovery after a failed update + +If the same verified ZIP cannot complete against the expected DFU device, stop remote attempts and recover the exact board/role by USB. Restore the backed-up identity and settings where supported, then repeat the complete verification. Never try a different board artifact as a recovery guess. -### Troubleshooting +## Next step -If the update stalls or fails, issue the following command from your phone or a standalone device, then try the process again: +Record the old and new versions, artifact filename/source, device identity, result, settings check, radio test, and recovery status in the repeater maintenance record. -``` -reboot -``` +## Verification limits -**Note:** For RAK 4631 boards with the Bootloader update, when following the OTA instructions and when you upload the update, you will likely get an error that the flash has failed. When scanning again for devices in the app, it will appear with a generic name (e.g., AdaDFU, RAK4631_DFU, etc.). Select this device and re-upload the update. +The Android recommendation and DFU settings are community field practice, not a current cross-platform test matrix. A firmware maintainer must reproduce the exact board/version/app combinations before this page can be marked verified. diff --git a/docs/privacy.md b/docs/privacy.md new file mode 100644 index 0000000..811441a --- /dev/null +++ b/docs/privacy.md @@ -0,0 +1,71 @@ +--- +title: Privacy at MeshCore Canada +description: Understand what the MeshCore Canada website, tools, and public contribution forms send or store. +audience: + - site-visitor + - contributor +task: understand-site-privacy +scope: canada-baseline +status: draft +owner: site-maintainers +last_reviewed: 2026-07-19 +review_by: 2027-01-15 +difficulty: beginner +estimated_time: 3 minutes +destructive: false +--- + +# Privacy at MeshCore Canada + +Most pages are static documentation hosted through GitHub Pages. Reading them +does not require a MeshCore Canada or GitHub account. + +## When data leaves your browser + +| Action | What is sent | Where it goes | +|---|---|---| +| Search this documentation | Your search words stay in the browser | Nowhere | +| Search for a place in the region tools | The place, airport code, or postal code you enter | OpenStreetMap Nominatim or geocoder.ca, after you agree | +| Load street-map tiles | Your IP address and the visible map area | OpenStreetMap, after you agree | +| Open an external link | Normal web request information | The named external service | +| Submit an idea or region proposal | The text and proposal details shown at review, plus anti-spam verification | MeshCore Canada’s submission service, Cloudflare Turnstile, and a public GitHub issue | + +The configurator also supports manual region selection. You do not need an +external place search to produce region commands. + +## Public submissions + +Ideas and boundary proposals become public GitHub issues. Review the preview +before submitting. Do not include passwords, private keys, home addresses, +private coordinates, or other personal information. + +The submission service may keep limited security and rate-limit logs. The +project has not yet published a retention period, so this page remains marked +**draft** until maintainers approve one. + +## Browser storage + +The site may save non-sensitive journey progress or an idea draft only when the +page explains the feature and you choose to use it. Passwords, private keys, +anti-spam tokens, and exact location searches are excluded. + +You can clear saved site data through your browser settings. The idea form also +provides a **Clear draft** action. + +## Network data tools + +Links to CoreScope, live.meshcore.ca, dev.meshcore.ca, MQTT services, flashers, +and community sites leave meshcore.ca. Those services have their own operators +and policies. The link text identifies the destination before you open it. + +## Analytics + +MeshCore Canada does not currently enable site analytics in this documentation. +If privacy-preserving analytics are proposed later, they must be documented +here before activation. They must not collect exact locations, command +contents, credentials, private keys, or raw form text. + +## Questions or corrections + +[Share a privacy correction](submit-idea.md) without a GitHub account, or +[open an issue on GitHub](https://github.com/MeshCore-ca/MeshCore-Canada/issues). diff --git a/docs/provinces/alberta.md b/docs/provinces/alberta.md index 565d88a..d68a0d5 100644 --- a/docs/provinces/alberta.md +++ b/docs/provinces/alberta.md @@ -1,62 +1,135 @@ -# MeshCore Communities in Alberta - -This page lists MeshCore communities in Alberta. - -If you know of a community that is missing or needs an update, open an update request through [Contributing](../contributing.md). - -## Communities - -### Calgary and Area MeshCore - -| Field | Value | -|-------|-------| -| Region | Calgary and area | -| Status | Active | -| Radio preset | `USA/Canada (Recommended)` | -| Raw radio values | `910.525 MHz / 62.5 kHz / SF7 / CR5` | -| Path hash mode | `3-byte` | -| Telegram | Calgary topic in [Mesh Alberta](https://t.me/meshtAlta) | -| Discord | Canada - Calgary, Alberta & Area regional channel in the MeshCore Discord | - -### YQLMesh - -| Field | Value | -|-------|-------| -| Region | Lethbridge | -| Status | Active | -| Radio preset | `USA/Canada (Recommended)` | -| Raw radio values | `910.525 MHz / 62.5 kHz / SF7 / CR5` | -| Path hash mode | `3-byte` | -| Website | | -| Facebook | [YQLMesh Community](https://www.facebook.com/groups/839542635605360) | -| Discord | | - -### Southern Alberta - -| Field | Value | -|-------|-------| -| Region | Southern Alberta, including Cardston, Magrath, Raymond, and nearby areas | -| Status | Active | -| Radio preset | `USA/Canada (Recommended)` | -| Raw radio values | `910.525 MHz / 62.5 kHz / SF7 / CR5` | -| Path hash mode | `3-byte` | -| Discord | Canada - Southern Alberta regional channel in the MeshCore Discord | -| Telegram | Cardston topic in [Mesh Alberta](https://t.me/meshtAlta) | - -### YYC MeshCore Discord Group - -| Field | Value | -|-------|-------| -| Region | Calgary | -| Status | Active | -| Radio preset | `USA/Canada (Recommended)` | -| Raw radio values | `910.525 MHz / 62.5 kHz / SF7 / CR5` | -| Path hash mode | `3-byte` | -| Discord | | -| Website | | - -## Province-Wide Contacts - -| Contact | Link | -|---------|------| -| Telegram | Alberta topic in [MeshCore Canada](https://t.me/MeshCoreCAN) | +--- +title: MeshCore communities in Alberta +description: Find MeshCore community contacts, service areas, and local settings for Alberta. +audience: + - community-seeker + - community-maintainer +task: browse-community-directory +scope: canada-baseline +status: draft +owner: directory-stewards +last_reviewed: 2026-07-19 +review_by: 2027-01-15 +difficulty: beginner +estimated_time: 2-5 minutes +destructive: false +page_styles: + - assets/styles/communities.css +--- + + + +# MeshCore communities in Alberta + +This directory currently has **4** listings: 4 active. + +All listings inherit the [Canada baseline](index.md#canada-baseline) unless a +card shows a local override. + +## Community listings + +
+
+
+

Calgary and Area MeshCore

+Active +
+

Calgary and area

+
+
Settings
+
Uses the Canada baseline
+
Last verified
+
Not yet verified
+
+

Contacts

+ +

+Contact check: Not yet verified +

+

Update this listing

+
+ +
+
+

Southern Alberta

+Active +
+

Southern Alberta, including Cardston, Magrath, Raymond, and nearby areas

+
+
Settings
+
Uses the Canada baseline
+
Last verified
+
Not yet verified
+
+

Contacts

+ +

+Contact check: Not yet verified +

+

Update this listing

+
+
+
+

YYC MeshCore Discord Group

+Active +
+

Calgary

+
+
Settings
+
Uses the Canada baseline
+
Last verified
+
Not yet verified
+
+

Contacts

+ +

+Contact check: Not yet verified +

+

Update this listing

+
+
+ +## Province-wide contacts + +
+

Telegram: Alberta topic in MeshCore Canada (external)

+

Contact check: Not yet verified

+
+ +## Add or update a listing + +[Send a community update](../submit-idea.md). No GitHub account is needed. + +The directory does not guess exact locations, languages, owners, or link health. +Fields remain marked unverified until a community steward reviews them. diff --git a/docs/provinces/british-columbia.md b/docs/provinces/british-columbia.md index c1966ae..b6c1898 100644 --- a/docs/provinces/british-columbia.md +++ b/docs/provinces/british-columbia.md @@ -1,18 +1,60 @@ -# MeshCore Communities in British Columbia +--- +title: MeshCore communities in British Columbia +description: Find MeshCore community contacts, service areas, and local settings for British Columbia. +audience: + - community-seeker + - community-maintainer +task: browse-community-directory +scope: canada-baseline +status: draft +owner: directory-stewards +last_reviewed: 2026-07-19 +review_by: 2027-01-15 +difficulty: beginner +estimated_time: 2-5 minutes +destructive: false +page_styles: + - assets/styles/communities.css +--- -This page lists MeshCore communities in British Columbia. + -If you know of a community that is missing or needs an update, open an update request through [Contributing](../contributing.md). +# MeshCore communities in British Columbia -## Communities +This directory currently has **1** listing: 1 active. -### Salish Mesh +All listings inherit the [Canada baseline](index.md#canada-baseline) unless a +card shows a local override. -| Field | Value | -|-------|-------| -| Region | Salish Sea and surrounding area | -| Status | Active | -| Radio preset | `USA/Canada (Recommended)` | -| Raw radio values | `910.525 MHz / 62.5 kHz / SF7 / CR5` | -| Path hash mode | `3-byte` | -| Website | | +## Community listings + +
+
+
+

Salish Mesh

+Active +
+

Salish Sea and surrounding area

+
+
Settings
+
Uses the Canada baseline
+
Last verified
+
Not yet verified
+
+

Contacts

+ +

+Contact check: Not yet verified +

+

Update this listing

+
+
+ +## Add or update a listing + +[Send a community update](../submit-idea.md). No GitHub account is needed. + +The directory does not guess exact locations, languages, owners, or link health. +Fields remain marked unverified until a community steward reviews them. diff --git a/docs/provinces/index.md b/docs/provinces/index.md index e0d80d9..fa1c062 100644 --- a/docs/provinces/index.md +++ b/docs/provinces/index.md @@ -1,50 +1,376 @@ -# Mesh Directory +--- +title: Find a MeshCore community in Canada +description: Search Canadian MeshCore communities by place, province, community name, or common alias. +audience: + - community-seeker + - community-maintainer +task: find-community +scope: canada-baseline +status: draft +owner: directory-stewards +last_reviewed: 2026-07-19 +review_by: 2027-01-15 +difficulty: beginner +estimated_time: 2-5 minutes +destructive: false +page_styles: + - assets/styles/communities.css +page_scripts: + - assets/javascripts/communities.js +--- -Find the nearest MeshCore Canada community page for your province or territory. Local pages can document region-specific rooms, repeaters, observers, contact points, and any settings that differ from the national onboarding baseline. + -!!! tip "Start local" - MeshCore Canada generally uses the USA/Canada radio preset and 3-byte path hashes, but local pages are the source of truth when a community documents a different preset or operating practice. +# Find a MeshCore community -## National Baseline +Search by place, province, community name, or a common alias. The full list +works without a map, location permission, or a GitHub account. -Use these defaults unless a local community page publishes a different setting: +
+16 listings +15 active +1 forming +1 local override +
-| Setting | Value | -|---------|-------| +
+ +
+ + +
+ + + + Showing 16 communities + +
+ + + +## Communities + +
+ + + + + + + + + + + + + + + + +
+ +## Canada baseline + +Use these settings only when your local community has not published an override. + +| Setting | Canada baseline | +|---|---| | Radio preset | `USA/Canada (Recommended)` | | Raw radio values | `910.525 MHz / 62.5 kHz / SF7 / CR5` | | Path hash mode | `3-byte` | -| CLI path setting | `set path.hash.mode 2` | - -## Provinces and Territories - -| Region | Directory status | -|--------|------------------| -| [British Columbia](british-columbia.md) | Active listing | -| [Alberta](alberta.md) | Active listings | -| [Saskatchewan](saskatchewan.md) | Needs community listing | -| [Manitoba](manitoba.md) | Needs community listing | -| [Ontario](ontario.md) | Active listings | -| [Quebec](quebec.md) | Active listings | -| [New Brunswick](new-brunswick.md) | Forming listing | -| [Nova Scotia](nova-scotia.md) | Active listing | -| [Prince Edward Island](prince-edward-island.md) | Needs community listing | -| [Newfoundland and Labrador](newfoundland-and-labrador.md) | Needs community listing | -| [Territories (YT / NT / NU)](territories.md) | Needs community listing | - -## Community Entry Format - -Community pages should use the same basic fields so newcomers can scan quickly: - -| Field | What to include | -|-------|-----------------| -| Name | Community or mesh name | -| Region | City, area, province, or coverage region | -| Status | Active, forming, testing, or needs update | -| Radio settings | Preset plus raw values when known | -| Path hash mode | Usually 3-byte for MeshCore Canada | -| Contact | Discord, Telegram, website, issue link, or maintainer | - -## Missing or outdated local information? - -Open an update request through [Contributing](../contributing.md) so the directory can stay current as Canadian MeshCore communities grow. +| Command-line path setting | `set path.hash.mode 2` | + +!!! warning "Check local settings first" + Nearby devices need matching settings. A card marked **Local override** + takes precedence over the Canada baseline after you confirm it with the + listed community. + +## Browse by province or territory + +
+ + + + + + + + + + + +
+ +## Add or update a listing + +Found missing or outdated information? +[Send a community update](../submit-idea.md). No GitHub account is needed. + +Directory contacts are community-provided external links. **Not yet verified** +means the directory stewards have preserved the listing but have not recorded a +recent contact check. Use the update action if a link has expired. diff --git a/docs/provinces/manitoba.md b/docs/provinces/manitoba.md index f7cb5b9..de1e0e2 100644 --- a/docs/provinces/manitoba.md +++ b/docs/provinces/manitoba.md @@ -1,17 +1,42 @@ -# MeshCore Communities in Manitoba +--- +title: MeshCore communities in Manitoba +description: Find MeshCore community contacts, service areas, and local settings for Manitoba. +audience: + - community-seeker + - community-maintainer +task: browse-community-directory +scope: canada-baseline +status: draft +owner: directory-stewards +last_reviewed: 2026-07-19 +review_by: 2027-01-15 +difficulty: beginner +estimated_time: 2-5 minutes +destructive: false +page_styles: + - assets/styles/communities.css +--- -This page is ready for Manitoba MeshCore community listings. + -## Current Status +# MeshCore communities in Manitoba -No Manitoba community listing has been submitted yet. +There is no reviewed community listing here yet. -If you operate or know of a Manitoba MeshCore group, open an update request through [Contributing](../contributing.md) with the community name, region, status, contact link, and radio settings. +
+

Help add the first listing

+

Share the community name, service area, status, and a public contact link.

+

Add a community

+

Browse all Canadian communities

+
-## Default Settings +Until a reviewed local listing publishes an override, start with the +[Canada baseline](index.md#canada-baseline) and confirm settings with nearby +operators before transmitting. -| Setting | Value | -|---------|-------| -| Radio preset | `USA/Canada (Recommended)` | -| Raw radio values | `910.525 MHz / 62.5 kHz / SF7 / CR5` | -| Path hash mode | `3-byte` | +## Add or update a listing + +[Send a community update](../submit-idea.md). No GitHub account is needed. + +The directory does not guess exact locations, languages, owners, or link health. +Fields remain marked unverified until a community steward reviews them. diff --git a/docs/provinces/new-brunswick.md b/docs/provinces/new-brunswick.md index bb0b4bd..6dc7d53 100644 --- a/docs/provinces/new-brunswick.md +++ b/docs/provinces/new-brunswick.md @@ -1,18 +1,63 @@ -# MeshCore Communities in New Brunswick +--- +title: MeshCore communities in New Brunswick +description: Find MeshCore community contacts, service areas, and local settings for New Brunswick. +audience: + - community-seeker + - community-maintainer +task: browse-community-directory +scope: canada-baseline +status: draft +owner: directory-stewards +last_reviewed: 2026-07-19 +review_by: 2027-01-15 +difficulty: beginner +estimated_time: 2-5 minutes +destructive: false +page_styles: + - assets/styles/communities.css +--- -This page lists MeshCore communities in New Brunswick. + -If you know of a community that is missing or needs an update, open an update request through [Contributing](../contributing.md). +# MeshCore communities in New Brunswick -## Communities +This directory currently has **1** listing: 1 forming. -### Southern New Brunswick +All listings inherit the [Canada baseline](index.md#canada-baseline) unless a +card shows a local override. -| Field | Value | -|-------|-------| -| Region | Fredericton, Saint John, Moncton, and nearby areas | -| Status | Forming | -| Radio preset | `USA/Canada (Recommended)` | -| Raw radio values | `910.525 MHz / 62.5 kHz / SF7 / CR5` | -| Path hash mode | `3-byte` | -| Facebook | [MESHCORE Saint John, N.B.](https://www.facebook.com/groups/613466831163684) | +## Community listings + +
+
+
+

Southern New Brunswick

+Forming +
+

Fredericton, Saint John, Moncton, and nearby areas

+
+
Settings
+
Uses the Canada baseline
+
Last verified
+
Not yet verified
+
+

+This group is forming. Contact it to learn what is working and where help is needed. +

+

Contacts

+ +

+Contact check: Not yet verified +

+

Update this listing

+
+
+ +## Add or update a listing + +[Send a community update](../submit-idea.md). No GitHub account is needed. + +The directory does not guess exact locations, languages, owners, or link health. +Fields remain marked unverified until a community steward reviews them. diff --git a/docs/provinces/newfoundland-and-labrador.md b/docs/provinces/newfoundland-and-labrador.md index 8694a1f..ff96e7a 100644 --- a/docs/provinces/newfoundland-and-labrador.md +++ b/docs/provinces/newfoundland-and-labrador.md @@ -1,17 +1,42 @@ -# MeshCore Communities in Newfoundland and Labrador +--- +title: MeshCore communities in Newfoundland and Labrador +description: Find MeshCore community contacts, service areas, and local settings for Newfoundland and Labrador. +audience: + - community-seeker + - community-maintainer +task: browse-community-directory +scope: canada-baseline +status: draft +owner: directory-stewards +last_reviewed: 2026-07-19 +review_by: 2027-01-15 +difficulty: beginner +estimated_time: 2-5 minutes +destructive: false +page_styles: + - assets/styles/communities.css +--- -This page is ready for Newfoundland and Labrador MeshCore community listings. + -## Current Status +# MeshCore communities in Newfoundland and Labrador -No Newfoundland and Labrador community listing has been submitted yet. +There is no reviewed community listing here yet. -If you operate or know of a Newfoundland and Labrador MeshCore group, open an update request through [Contributing](../contributing.md) with the community name, region, status, contact link, and radio settings. +
+

Help add the first listing

+

Share the community name, service area, status, and a public contact link.

+

Add a community

+

Browse all Canadian communities

+
-## Default Settings +Until a reviewed local listing publishes an override, start with the +[Canada baseline](index.md#canada-baseline) and confirm settings with nearby +operators before transmitting. -| Setting | Value | -|---------|-------| -| Radio preset | `USA/Canada (Recommended)` | -| Raw radio values | `910.525 MHz / 62.5 kHz / SF7 / CR5` | -| Path hash mode | `3-byte` | +## Add or update a listing + +[Send a community update](../submit-idea.md). No GitHub account is needed. + +The directory does not guess exact locations, languages, owners, or link health. +Fields remain marked unverified until a community steward reviews them. diff --git a/docs/provinces/nova-scotia.md b/docs/provinces/nova-scotia.md index 0a4ed7a..9a18f8e 100644 --- a/docs/provinces/nova-scotia.md +++ b/docs/provinces/nova-scotia.md @@ -1,18 +1,60 @@ -# MeshCore Communities in Nova Scotia +--- +title: MeshCore communities in Nova Scotia +description: Find MeshCore community contacts, service areas, and local settings for Nova Scotia. +audience: + - community-seeker + - community-maintainer +task: browse-community-directory +scope: canada-baseline +status: draft +owner: directory-stewards +last_reviewed: 2026-07-19 +review_by: 2027-01-15 +difficulty: beginner +estimated_time: 2-5 minutes +destructive: false +page_styles: + - assets/styles/communities.css +--- -This page lists MeshCore communities in Nova Scotia. + -If you know of a community that is missing or needs an update, open an update request through [Contributing](../contributing.md). +# MeshCore communities in Nova Scotia -## Communities +This directory currently has **1** listing: 1 active. -### Lunenburg County Mesh +All listings inherit the [Canada baseline](index.md#canada-baseline) unless a +card shows a local override. -| Field | Value | -|-------|-------| -| Region | Lunenburg County | -| Status | Active | -| Radio preset | `USA/Canada (Recommended)` | -| Raw radio values | `910.525 MHz / 62.5 kHz / SF7 / CR5` | -| Path hash mode | `3-byte` | -| Website | | +## Community listings + +
+
+
+

Lunenburg County Mesh

+Active +
+

Lunenburg County

+
+
Settings
+
Uses the Canada baseline
+
Last verified
+
Not yet verified
+
+

Contacts

+ +

+Contact check: Not yet verified +

+

Update this listing

+
+
+ +## Add or update a listing + +[Send a community update](../submit-idea.md). No GitHub account is needed. + +The directory does not guess exact locations, languages, owners, or link health. +Fields remain marked unverified until a community steward reviews them. diff --git a/docs/provinces/ontario.md b/docs/provinces/ontario.md index 69233e5..b84f509 100644 --- a/docs/provinces/ontario.md +++ b/docs/provinces/ontario.md @@ -1,42 +1,104 @@ -# MeshCore Communities in Ontario - -This page lists MeshCore communities in Ontario. - -If you know of a community that is missing or needs an update, open an update request through [Contributing](../contributing.md). - -## Communities - -### Greater Ottawa Mesh Enthusiasts - -| Field | Value | -|-------|-------| -| Region | Eastern Ontario and Western Quebec | -| Status | Active | -| Radio preset | `USA/Canada (Recommended)` | -| Raw radio values | `910.525 MHz / 62.5 kHz / SF7 / CR5` | -| Path hash mode | `3-byte` | -| Discord | | -| Website | | - -### GTA+-Lora-Meshes - -| Field | Value | -|-------|-------| -| Region | Southern Ontario | -| Status | Active | -| Radio preset | `USA/Canada (Recommended)` | -| Raw radio values | `910.525 MHz / 62.5 kHz / SF7 / CR5` | -| Path hash mode | `3-byte` | -| Discord | | - -### Quinte Mesh Network - -| Field | Value | -|-------|-------| -| Region | Quinte Region, including Belleville, Trenton, Prince Edward County, and nearby areas | -| Status | Active | -| Radio preset | `USA/Canada (Recommended)` | -| Raw radio values | `910.525 MHz / 62.5 kHz / SF7 / CR5` | -| Path hash mode | `3-byte` | -| Discord | | -| Website | | +--- +title: MeshCore communities in Ontario +description: Find MeshCore community contacts, service areas, and local settings for Ontario. +audience: + - community-seeker + - community-maintainer +task: browse-community-directory +scope: canada-baseline +status: draft +owner: directory-stewards +last_reviewed: 2026-07-19 +review_by: 2027-01-15 +difficulty: beginner +estimated_time: 2-5 minutes +destructive: false +page_styles: + - assets/styles/communities.css +--- + + + +# MeshCore communities in Ontario + +This directory currently has **3** listings: 3 active. + +All listings inherit the [Canada baseline](index.md#canada-baseline) unless a +card shows a local override. + +## Community listings + +
+ +
+
+

GTA+-Lora-Meshes

+Active +
+

Southern Ontario

+
+
Settings
+
Uses the Canada baseline
+
Last verified
+
Not yet verified
+
+

Contacts

+ +

+Contact check: Not yet verified +

+

Update this listing

+
+ +
+ +## Add or update a listing + +[Send a community update](../submit-idea.md). No GitHub account is needed. + +The directory does not guess exact locations, languages, owners, or link health. +Fields remain marked unverified until a community steward reviews them. diff --git a/docs/provinces/prince-edward-island.md b/docs/provinces/prince-edward-island.md index 8d9e090..2c5a327 100644 --- a/docs/provinces/prince-edward-island.md +++ b/docs/provinces/prince-edward-island.md @@ -1,17 +1,42 @@ -# MeshCore Communities in Prince Edward Island +--- +title: MeshCore communities in Prince Edward Island +description: Find MeshCore community contacts, service areas, and local settings for Prince Edward Island. +audience: + - community-seeker + - community-maintainer +task: browse-community-directory +scope: canada-baseline +status: draft +owner: directory-stewards +last_reviewed: 2026-07-19 +review_by: 2027-01-15 +difficulty: beginner +estimated_time: 2-5 minutes +destructive: false +page_styles: + - assets/styles/communities.css +--- -This page is ready for Prince Edward Island MeshCore community listings. + -## Current Status +# MeshCore communities in Prince Edward Island -No Prince Edward Island community listing has been submitted yet. +There is no reviewed community listing here yet. -If you operate or know of a Prince Edward Island MeshCore group, open an update request through [Contributing](../contributing.md) with the community name, region, status, contact link, and radio settings. +
+

Help add the first listing

+

Share the community name, service area, status, and a public contact link.

+

Add a community

+

Browse all Canadian communities

+
-## Default Settings +Until a reviewed local listing publishes an override, start with the +[Canada baseline](index.md#canada-baseline) and confirm settings with nearby +operators before transmitting. -| Setting | Value | -|---------|-------| -| Radio preset | `USA/Canada (Recommended)` | -| Raw radio values | `910.525 MHz / 62.5 kHz / SF7 / CR5` | -| Path hash mode | `3-byte` | +## Add or update a listing + +[Send a community update](../submit-idea.md). No GitHub account is needed. + +The directory does not guess exact locations, languages, owners, or link health. +Fields remain marked unverified until a community steward reviews them. diff --git a/docs/provinces/quebec.md b/docs/provinces/quebec.md index 2b0cfc2..07f4a61 100644 --- a/docs/provinces/quebec.md +++ b/docs/provinces/quebec.md @@ -1,64 +1,146 @@ -# MeshCore Communities in Quebec +--- +title: MeshCore communities in Quebec +description: Find MeshCore community contacts, service areas, and local settings for Quebec. +audience: + - community-seeker + - community-maintainer +task: browse-community-directory +scope: canada-baseline +status: draft +owner: directory-stewards +last_reviewed: 2026-07-19 +review_by: 2027-01-15 +difficulty: beginner +estimated_time: 2-5 minutes +destructive: false +page_styles: + - assets/styles/communities.css +--- -This page lists MeshCore communities in Quebec. + -If you know of a community that is missing or needs an update, open an update request through [Contributing](../contributing.md). +# MeshCore communities in Quebec -## Communities +This directory currently has **5** listings: 5 active. -### Mesh Quebec +All listings inherit the [Canada baseline](index.md#canada-baseline) unless a +card shows a local override. -| Field | Value | -|-------|-------| -| Region | Quebec | -| Status | Active | -| Radio preset | `USA/Canada (Recommended)` | -| Raw radio values | `910.525 MHz / 62.5 kHz / SF7 / CR5` | -| Path hash mode | `3-byte` | -| Website | | +## Community listings -### Montreal Mesh +
+
+
+

Mesh Quebec

+Active +
+

Quebec

+
+
Settings
+
Uses the Canada baseline
+
Last verified
+
Not yet verified
+
+

Contacts

+ +

+Contact check: Not yet verified +

+

Update this listing

+
+
+
+

Montreal Mesh

+Active +
+

Greater Montreal

+
+
Settings
+
Uses the Canada baseline
+
Last verified
+
Not yet verified
+
+

Contacts

+ +

+Contact check: Not yet verified +

+

Update this listing

+
+ + +
+
+

Réseau Libre

+Active +
+

Montreal

+
+
Settings
+
Uses the Canada baseline
+
Last verified
+
Not yet verified
+
+

Contacts

+ +

+Contact check: Not yet verified +

+

Update this listing

+
+
-| Field | Value | -|-------|-------| -| Region | Greater Montreal | -| Status | Active | -| Radio preset | `USA/Canada (Recommended)` | -| Raw radio values | `910.525 MHz / 62.5 kHz / SF7 / CR5` | -| Path hash mode | `3-byte` | -| Website | | +## Add or update a listing -### Réseau Mesh de la Capitale YQB +[Send a community update](../submit-idea.md). No GitHub account is needed. -| Field | Value | -|-------|-------| -| Region | Quebec City | -| Status | Active | -| Radio preset | `USA/Canada (Recommended)` | -| Raw radio values | `910.525 MHz / 62.5 kHz / SF7 / CR5` | -| Path hash mode | `3-byte` | -| Discord | | - -### Réseau Mesh du Saguenay Lac st-Jean YTF - -| Field | Value | -|-------|-------| -| Region | Saguenay–Lac-Saint-Jean (YTF) | -| Status | Active | -| Radio preset | `USA/Canada (Recommended)` | -| Raw radio values | `910.525 MHz / 62.5 kHz / SF7 / CR5` | -| Path hash mode | `3-byte` | -| Discord | | -| Facebook | | -| MeshMapper | | - -### Réseau Libre - -| Field | Value | -|-------|-------| -| Region | Montreal | -| Status | Active | -| Radio preset | `USA/Canada (Recommended)` | -| Raw radio values | `910.525 MHz / 62.5 kHz / SF7 / CR5` | -| Path hash mode | `3-byte` | -| Website | | +The directory does not guess exact locations, languages, owners, or link health. +Fields remain marked unverified until a community steward reviews them. diff --git a/docs/provinces/saskatchewan.md b/docs/provinces/saskatchewan.md index c216744..60f622e 100644 --- a/docs/provinces/saskatchewan.md +++ b/docs/provinces/saskatchewan.md @@ -1,18 +1,68 @@ -# MeshCore Communities in Saskatchewan +--- +title: MeshCore communities in Saskatchewan +description: Find MeshCore community contacts, service areas, and local settings for Saskatchewan. +audience: + - community-seeker + - community-maintainer +task: browse-community-directory +scope: canada-baseline +status: draft +owner: directory-stewards +last_reviewed: 2026-07-19 +review_by: 2027-01-15 +difficulty: beginner +estimated_time: 2-5 minutes +destructive: false +page_styles: + - assets/styles/communities.css +--- -This page lists MeshCore communities in Saskatchewan. + -If you know of a community that is missing or needs an update, open an update request through [Contributing](../contributing.md). +# MeshCore communities in Saskatchewan -## Communities +This directory currently has **1** listing: 1 active. -### StoonMesh +All listings inherit the [Canada baseline](index.md#canada-baseline) unless a +card shows a local override. -| Field | Value | -|-------|-------| -| Region | Central Saskatchewan | -| Status | Active | -| Radio preset | `USA/Canada (Recommended)` | -| Raw radio values | `910.525 MHz / 62.5 kHz / SF7 / CR5` | -| Path hash mode | `1-byte` | -| Discord | | +!!! warning "Local setting differs from the Canada baseline" + One community on this page publishes a local setting override. Confirm + the current setting with its contact before configuring or changing a node. + +## Community listings + +
+
+
+

StoonMesh

+Active +
+

Central Saskatchewan

+
+
Settings
+
Path hash mode: 1-byte
+
Last verified
+
Not yet verified
+
+
+Local settings override +

Confirm this setting with the community before changing a node.

+
+

Contacts

+ +

+Contact check: Not yet verified +

+

Update this listing

+
+
+ +## Add or update a listing + +[Send a community update](../submit-idea.md). No GitHub account is needed. + +The directory does not guess exact locations, languages, owners, or link health. +Fields remain marked unverified until a community steward reviews them. diff --git a/docs/provinces/territories.md b/docs/provinces/territories.md index 4b14455..bdd0077 100644 --- a/docs/provinces/territories.md +++ b/docs/provinces/territories.md @@ -1,17 +1,42 @@ -# MeshCore Communities in the Territories +--- +title: MeshCore communities in the territories +description: Find MeshCore community contacts, service areas, and local settings for the territories. +audience: + - community-seeker + - community-maintainer +task: browse-community-directory +scope: canada-baseline +status: draft +owner: directory-stewards +last_reviewed: 2026-07-19 +review_by: 2027-01-15 +difficulty: beginner +estimated_time: 2-5 minutes +destructive: false +page_styles: + - assets/styles/communities.css +--- -This page is ready for Yukon, Northwest Territories, and Nunavut MeshCore community listings. + -## Current Status +# MeshCore communities in the territories -No territories community listing has been submitted yet. +There is no reviewed community listing here yet. -If you operate or know of a MeshCore group in Yukon, Northwest Territories, or Nunavut, open an update request through [Contributing](../contributing.md) with the community name, region, status, contact link, and radio settings. +
+

Help add the first listing

+

Share the community name, service area, status, and a public contact link.

+

Add a community

+

Browse all Canadian communities

+
-## Default Settings +Until a reviewed local listing publishes an override, start with the +[Canada baseline](index.md#canada-baseline) and confirm settings with nearby +operators before transmitting. -| Setting | Value | -|---------|-------| -| Radio preset | `USA/Canada (Recommended)` | -| Raw radio values | `910.525 MHz / 62.5 kHz / SF7 / CR5` | -| Path hash mode | `3-byte` | +## Add or update a listing + +[Send a community update](../submit-idea.md). No GitHub account is needed. + +The directory does not guess exact locations, languages, owners, or link health. +Fields remain marked unverified until a community steward reviews them. diff --git a/docs/resources/getting-started.md b/docs/resources/getting-started.md index de5286f..8c21a60 100644 --- a/docs/resources/getting-started.md +++ b/docs/resources/getting-started.md @@ -1,97 +1,38 @@ -# Getting Started with MeshCore in Canada - -!!! info "This page is a work in progress" - More hardware-specific walkthroughs are still being added. These network settings are the current MeshCore Canada baseline. - -## Welcome to MeshCore! - -MeshCore Canada communities generally use the same radio preset and path settings so companions, repeaters, room servers, and observers can hear each other reliably. - -## Step 1: Understand the Basics - -MeshCore devices must match the local mesh's radio settings before they can join the network. For MeshCore Canada, start with these defaults unless your local community page lists a different setting. - -| Setting | MeshCore Canada value | -|---------|------------------------| -| Radio preset | `USA/Canada (Recommended)` | -| Raw radio values | `910.525 MHz / 62.5 kHz / SF7 / CR5` | -| Path hash / advert path mode | `3-byte` | -| CLI path setting | `set path.hash.mode 2` | - -!!! warning "Preset must match" - A device on another regional preset may appear to be configured correctly but will not hear the MeshCore Canada network. Pick **USA/Canada (Recommended)** in apps or config tools. If the tool only exposes raw radio fields, use `910.525 MHz`, `62.5 kHz`, `SF7`, and `CR5`. - -!!! note "Why 3-byte path hashes?" - MeshCore Canada recommends 3-byte path hashes for better behavior on larger repeater-backed networks. Companion devices often need this changed manually. Repeaters, room servers, and standalone observers should have path hash mode set during onboarding. - -## Step 2: Get Your Hardware - -Choose a role: - -- **Companion**: the handheld or mobile device you use to send and receive messages. -- **Repeater**: a fixed node that extends mesh coverage. -- **Room server**: a fixed node that hosts rooms and can also observe mesh traffic. -- **Observer**: a device or host service that forwards packets to the MeshCore.ca MQTT analyzer. - -Start with [Recommended Companions](../hardware/recommended-companions.md) if you want a personal device, or [Recommended Repeaters](../hardware/recommended-repeaters.md) if you are building a fixed node. - -## Step 3: Flash the Firmware - -Flash firmware for the role you need, then apply the MeshCore Canada network settings before judging whether the device works. - -For repeaters, room servers, and standalone observers, include this first-run CLI setting: - -```text -set path.hash.mode 2 -``` - -If you are reusing a device with retained preferences or configuring firmware through the CLI, also run `set radio 910.525,62.5,7,5`. - -For companion devices, set the radio preset to **USA/Canada (Recommended)** and set path hash mode to **3-byte** in the companion app or config tool. If you are configuring a companion through a CLI that supports MeshCore settings, use: - -```text -set path.hash.mode 2 -``` - -After changing radio parameters, reboot the device. - -### Optional repeater loop protection - -Loop detection is not part of the Canada-wide basic setup. MeshCore leaves it off by default. If local operators are troubleshooting a packet storm caused by a looping repeater, firmware 1.14 and newer can use: - -```text -set loop.detect moderate -``` - -This is a troubleshooting safeguard for repeaters, not a setting to apply to every new device. Coordinate the change with the local mesh, and leave it off when there is no loop problem. See the [upstream MeshCore CLI reference](https://github.com/meshcore-dev/MeshCore/blob/main/docs/cli_commands.md#view-or-change-this-nodes-loop-detection) for the available modes. - -## Step 4: Configure Your Role - -| Role | Next setup step | -|------|-----------------| -| Companion | Pair it with your app, send an advert, and ask a nearby user to confirm they can see you | -| Repeater | Pick a clear node name, test from the ground, then install it where it has good antenna visibility | -| Room server | Configure rooms, verify it still uses the local mesh settings, and document who maintains it | -| Observer | Follow [Analyzer & MQTT](../analyzer/intro.md) and verify it appears in CoreScope | - -## Step 5: Find or Start a Community - -Browse the [Mesh Directory](../provinces/index.md) and check your province or nearby region. If a local community documents a different preset, follow the local community setting for that mesh. - -If your region is missing, use [Contributing](../contributing.md) to request a new listing with the community name, region, status, contacts, and radio settings. - -## Step 6: Get on the Air - -Send an advert after configuration so nearby nodes learn your identity and route. If you do not see other users, re-check the radio preset first, then confirm the path hash mode is set to 3-byte. - -## First Success Checklist - -- Your device shows the **USA/Canada (Recommended)** preset, or the raw values match `910.525 MHz / 62.5 kHz / SF7 / CR5`. -- Path hash mode is **3-byte**. -- The device was rebooted after radio changes. -- A nearby community member or second known-good device can see your advert. -- If this is an observer, [CoreScope](https://live.meshcore.ca/#/observers) shows the expected node name and IATA region. - -## Need Help? - -Open an issue or update request through [Contributing](../contributing.md), or ask in your local MeshCore Canada community channel. +--- +title: Getting started with MeshCore in Canada +description: Continue from the former Getting Started URL to the canonical MeshCore Canada Start journey. +audience: + - newcomer +task: continue-to-start +scope: canada-baseline +status: legacy +owner: docs-ux +last_reviewed: 2026-07-19 +review_by: 2027-07-19 +difficulty: beginner +estimated_time: less than 1 minute +destructive: false +--- + +# Getting Started has moved + +The current beginner guide is now **Start**. It keeps role choice, Canada +settings, verification, and help in one journey. + +[:octicons-arrow-right-24: **Open Start**](../start/index.md){ .md-button .md-button--primary } + +## Go directly to your role + +- [Send and receive messages with a companion](../start/companion.md) +- [Improve local coverage with a repeater](../start/repeater.md) +- [Host a persistent room](../start/room-server.md) +- [Send network data to public tools with an observer](../start/observer.md) +- [Compare roles if you are not sure](../start/choose-a-goal.md) + +This page remains available so existing bookmarks and external links do not +break. + +## Next step + +[Choose a role in Start](../start/index.md), then finish its verification +check before moving to advanced tools. diff --git a/docs/resources/glossary.md b/docs/resources/glossary.md index 610ea61..053bce6 100644 --- a/docs/resources/glossary.md +++ b/docs/resources/glossary.md @@ -1,66 +1,121 @@ -# Glossary - -A quick reference for common terms used across the MeshCore community. +--- +title: MeshCore glossary +description: Find plain-language definitions for MeshCore devices, radio settings, paths, and network data terms. +audience: + - newcomer + - meshcore-user +task: define-meshcore-term +scope: canada-baseline +status: verified +owner: docs-ux +last_reviewed: 2026-07-19 +review_by: 2027-01-19 +evidence: Existing MeshCore Canada documentation terminology +difficulty: beginner +estimated_time: 2-10 minutes +destructive: false +--- + +# MeshCore glossary + +Use your browser's **Find** command to jump to a term. Definitions are short; +the linked guides explain how to use each setting or device. + +## A-D **Advert** -: A MeshCore announcement packet that helps nearby nodes learn about a device and route. +: A MeshCore announcement packet that helps nearby nodes learn about a device + and its path. **Bandwidth** -: A LoRa radio setting. MeshCore Canada baseline raw settings use `62.5 kHz`. +: A LoRa radio setting that affects how much radio spectrum a transmission + uses. Use the value from your local settings or the configurator. **Broker** -: An MQTT server. MeshCore.ca uses `mqtt1.meshcore.ca` and `mqtt2.meshcore.ca` for redundant observer publishing. +: A service that receives and distributes messages for Message Queuing + Telemetry Transport (MQTT). -**Coding Rate (CR)** -: A LoRa error-correction setting. MeshCore Canada baseline raw settings use `CR5`. +**Coding rate (CR)** +: A LoRa error-correction setting. Use the value from your local settings or + the configurator. **Companion** -: A portable MeshCore device used with a phone, tablet, desktop app, web app, or serial client to send and receive messages. +: A personal MeshCore device used to send and receive messages. It often + connects to an app and does not relay mesh traffic. **CoreScope** -: The MeshCore.ca live tools for checking observers, packet flow, and map data. +: MeshCore Canada's public tools for viewing observers, packets, nodes, and + map data. + +## F-M -**IATA Code** -: A real 3-letter airport code used by MeshCore.ca observers to tag their region, such as `YOW`, `YYZ`, or `YVR`. +**Firmware** +: Software installed on a device. MeshCore firmware is built for a specific + board and role. -**ISM Band** -: Industrial, Scientific, and Medical radio spectrum used by many low-power devices. You are responsible for legal operation in your location. +**Industrial, scientific, and medical (ISM) band** +: Radio spectrum used by many low-power devices. Legal use still depends on + location, equipment, and operating conditions. -**JWT** -: JSON Web Token. MeshCore.ca MQTT paths use token authentication instead of a static username/password. +**JSON Web Token (JWT)** +: A time-limited credential used by supported MeshCore Canada data services. + Treat it as sensitive. + +**Location code** +: A short code that groups observer data by area. Some observer guides call + this an IATA code because they use three-letter airport codes. **LoRa** -: Long Range radio modulation used by MeshCore devices. +: The long-range, low-power radio modulation used by MeshCore devices. + +**Mesh network** +: A network in which supported devices can relay traffic instead of relying + on one central radio. -**Mesh Network** -: A network where devices can relay traffic for each other instead of depending on one central access point. +**Message Queuing Telemetry Transport (MQTT)** +: A publish-and-subscribe protocol used by observers to send network data to + public tools. -**MQTT** -: A lightweight publish/subscribe messaging protocol used by MeshCore.ca observers to publish packet telemetry. +## N-R **Node** : Any MeshCore device on the network. **Observer** -: A MeshCore radio or host service that publishes mesh packet telemetry to MQTT for live tools. +: A radio or host service that listens for MeshCore traffic and sends network + data to public tools. It does not relay mesh traffic. -**Path Hash Mode** -: The MeshCore setting that controls advert path identifier size. MeshCore Canada recommends **3-byte**, which is `set path.hash.mode 2` in the CLI. +**Path hash mode** +: The MeshCore setting that controls the size of identifiers in advert paths. + Use the value from the current local or Canadian configuration. **Preset** -: A named group of radio settings. MeshCore Canada generally uses `USA/Canada (Recommended)`. +: A named group of radio settings. **Repeater** -: A fixed device that relays packets to extend mesh coverage. +: A normally fixed MeshCore device that relays packets to extend network + coverage. + +**Room server** +: A MeshCore device that keeps a shared room available to companions. Its + main job is hosting the room, not extending coverage. + +## S-W + +**Signal-to-noise ratio (SNR)** +: A measurement comparing a received signal with background noise. -**Room Server** -: A MeshCore node that hosts group chat rooms. It may also repeat or observe traffic depending on its configuration. +**Spreading factor (SF)** +: A LoRa setting that affects airtime, data rate, and reception. Use the value + from your local settings or the configurator. -**SNR** -: Signal-to-noise ratio, a measure of received signal quality. +**WebSocket** +: A long-lived web connection. Some MQTT clients use WebSockets to reach a + broker through normal web network paths. -**Spreading Factor (SF)** -: A LoRa setting that affects range, airtime, and data rate. MeshCore Canada baseline raw settings use `SF7`. +## Related pages -**WebSockets** -: A transport used by the MeshCore.ca MQTT brokers on port `443`, which is often easier to pass through normal internet connections. +- [Compare MeshCore roles](../meshcore/general-meshcore-roles.md) +- [Read common questions](../meshcore/general-faq.md) +- [Open the region standard](../config/standard.md) +- [Choose an observer method](../analyzer/intro.md) diff --git a/docs/resources/links.md b/docs/resources/links.md index 669a799..5a680cf 100644 --- a/docs/resources/links.md +++ b/docs/resources/links.md @@ -1,61 +1,81 @@ -# Useful Links +--- +title: MeshCore links and services +description: Open reviewed MeshCore Canada, official MeshCore, network-tool, and Canadian radio reference links. +audience: + - meshcore-user + - mesh-operator + - documentation-contributor +task: find-meshcore-resource +scope: canada-baseline +status: verified +owner: docs-ux +last_reviewed: 2026-07-19 +review_by: 2026-10-19 +evidence: External destinations opened successfully on 2026-07-19 +link_checked: 2026-07-19 +difficulty: beginner +estimated_time: 2-5 minutes +destructive: false +--- -This page collects common MeshCore, MeshCore Canada, and supporting radio resources. +# MeshCore links and services + +Use this page when a guide sends you to another site. External links are +labelled and open in a new tab. + +
+ +**Before opening an external service:** you are leaving this documentation +site. Check that service's privacy terms before entering account details, +device data, or a location. + +
## MeshCore Canada -| Resource | Link | -|----------|------| -| MeshCore Canada site | | -| MeshCore Canada forum | | -| Live CoreScope tools | | -| MeshCore Canada GitHub | | -| Add or update a community | [Contributing](../contributing.md) | -| Mesh Directory | [Browse provinces and territories](../provinces/index.md) | +| Service | Use | Link | +|---|---|---| +| MeshCore Canada forum | Search public discussions or ask for help. | [Open the forum (external)](https://forum.meshcore.ca/){ target="_blank" rel="noopener" } | +| CoreScope | View public observer, packet, node, and map data. | [Open CoreScope (external)](https://live.meshcore.ca/){ target="_blank" rel="noopener" } | +| GitHub repository | Review source, issues, and site changes. A GitHub account may be needed to contribute there. | [Open the repository (external)](https://github.com/MeshCore-ca/MeshCore-Canada){ target="_blank" rel="noopener" } | + +These destinations were checked on **2026-07-19**. ## Official MeshCore -| Resource | Link | -|----------|------| -| MeshCore website | | -| MeshCore docs | | -| MeshCore source | | -| MeshCore GitHub organization | | -| MeshCore Flasher | | -| Repeater / room-server USB config | | -| MeshCore web app | | - -## MeshCore.ca Analyzer - -| Resource | Link | -|----------|------| -| Observer setup overview | [Analyzer & MQTT](../analyzer/intro.md) | -| MQTT data collection and access | [Data Collection & Access](../analyzer/data-collection-access.md) | -| Direct MQTT firmware | [MQTT Firmware](../analyzer/builds/mqtt-firmware.md) | -| MCtoMQTT host bridge | [MCtoMQTT](../analyzer/builds/mctomqtt.md) | -| Home Assistant integration setup | [MeshCore-HA](../analyzer/builds/meshcore-ha.md) | -| PyMC setup | [PyMC](../analyzer/builds/pymc.md) | -| RemoteTerm setup | [RemoteTerm](../analyzer/remoteterm.md) | -| IATA code reference | [IATA Region Codes](../analyzer/iata-codes.md) | - -## Radio And Regulatory References - -| Resource | Link | -|----------|------| -| Innovation, Science and Economic Development Canada | | -| Radio Amateurs of Canada | | - -!!! note "Regulatory links" - MeshCore Canada docs are not legal advice. Check current ISED rules and any licence conditions that apply to your station before changing power, antenna gain, frequency, or duty cycle. - -## Tools - -| Tool | Use | -|------|-----| -| MeshCore Flasher | Flash supported MeshCore firmware and custom binaries | -| MeshCore USB config | Configure repeaters and room servers over serial | -| RemoteTerm | Manage radios and optionally publish packets through Community MQTT | -| CoreScope | Check observers, packets, and map visibility | -| MQTT clients | Advanced broker diagnostics when troubleshooting observer paths | - -When in doubt, start with [Getting Started](getting-started.md), then move to the hardware or observer guide that matches your device role. +| Service | Use | Link | +|---|---|---| +| MeshCore website | Read the official project introduction and current project links. | [Open MeshCore.io (external)](https://meshcore.io/){ target="_blank" rel="noopener" } | +| MeshCore documentation | Read upstream device, app, and protocol guidance. | [Open official docs (external)](https://docs.meshcore.io/){ target="_blank" rel="noopener" } | +| MeshCore source | Review the official firmware source and releases. | [Open MeshCore source (external)](https://github.com/meshcore-dev/MeshCore){ target="_blank" rel="noopener" } | +| MeshCore Flasher | Flash supported devices in a compatible browser. Read the relevant setup guide first. | [Open the flasher (external)](https://flasher.meshcore.io/){ target="_blank" rel="noopener" } | +| USB configuration tool | Configure a supported repeater or room server over USB. | [Open USB configuration (external)](https://config.meshcore.io/){ target="_blank" rel="noopener" } | +| MeshCore web app | Use the official web application with a supported companion. | [Open the web app (external)](https://app.meshcore.nz/){ target="_blank" rel="noopener" } | + +These destinations were checked on **2026-07-19**. Hardware support and +tool behaviour can change; follow the matching MeshCore Canada setup guide +before flashing or configuring a device. + +## MeshCore Canada guides - [Start with MeshCore](../start/index.md) - [Find a community](../provinces/index.md) - [Use the repeater configurator](../config/index.md) - [Contribute a correction or idea](../contributing.md) ## Observer guides | Task | Guide | |---|---| | Choose an observer method | [Observer setup](../analyzer/intro.md) | | Understand what data is collected | [Data collection and access](../analyzer/data-collection-access.md) | | Install direct observer firmware | [MQTT firmware](../analyzer/builds/mqtt-firmware.md) | | Bridge a radio through a host | [MCtoMQTT](../analyzer/builds/mctomqtt.md) | | Connect Home Assistant | [MeshCore-HA](../analyzer/builds/meshcore-ha.md) | | Run a Python-based observer | [PyMC](../analyzer/builds/pymc.md) | | Manage a radio remotely | [RemoteTerm](../analyzer/remoteterm.md) | | Choose an observer location code | [Location-code reference](../analyzer/iata-codes.md) | | Check the final result | [Check your observer](../analyzer/verify.md) | + +## Radio and regulatory references + +| Organization | Use | Link | +|---|---|---| +| Innovation, Science and Economic Development Canada (ISED) | Find current Government of Canada spectrum and equipment information. | [Open ISED (external)](https://ised-isde.canada.ca/){ target="_blank" rel="noopener" } | +| Radio Amateurs of Canada | Find Canadian amateur-radio information. | [Open RAC (external)](https://www.rac.ca/){ target="_blank" rel="noopener" } | + +These destinations were checked on **2026-07-19**. + +
+ +MeshCore Canada documentation is not legal advice. Check current ISED rules +and any licence conditions that apply before changing frequency, power, +antenna gain, or duty cycle. + +
+ +## Next step + +If you are new, go to [Start with MeshCore](../start/index.md). If a destination +is broken or misleading, [suggest a change](../submit-idea.md). diff --git a/docs/robots.txt b/docs/robots.txt new file mode 100644 index 0000000..0ace24e --- /dev/null +++ b/docs/robots.txt @@ -0,0 +1,4 @@ +User-agent: * +Allow: / + +Sitemap: https://meshcore.ca/sitemap.xml diff --git a/docs/start/choose-a-goal.md b/docs/start/choose-a-goal.md new file mode 100644 index 0000000..5987146 --- /dev/null +++ b/docs/start/choose-a-goal.md @@ -0,0 +1,64 @@ +--- +title: Choose a MeshCore role +description: Compare companions, repeaters, room servers, and observers by the job you want to do. +audience: + - newcomer + - first-time-user +task: choose-device-role +scope: upstream-meshcore +status: verified +owner: docs-ux +last_reviewed: 2026-07-19 +review_by: 2027-07-19 +tested_with: + content_baseline: f608cfe +difficulty: beginner +estimated_time: 2-3 minutes +destructive: false +--- + +# Choose a MeshCore role + +Choose by outcome, not by product name. + +| What you want to do | Role | Start | +|---|---|---| +| Send and receive messages | Companion | [Companion path](companion.md) | +| Improve coverage for nearby users | Repeater | [Repeater path](repeater.md) | +| Host a persistent shared room | Room server | [Room-server path](room-server.md) | +| Send network data to public tools | Observer | [Observer path](observer.md) | + +## Compare the roles + +| Role | Main job | Mobile or fixed | Routes mesh traffic? | Phone or computer | +|---|---|---|---|---| +| Companion | Personal messaging | Usually mobile | No | Usually pairs with a phone; some devices are standalone | +| Repeater | Extend coverage | Fixed | Yes | Needed for setup and maintenance | +| Room server | Keep a shared room available | Usually fixed | Not its primary job | A companion connects to the room | +| Observer | Feed public network tools | Usually fixed | No | Depends on the observer method | + +## Still not sure? + +Start by reading the [companion path](companion.md) if your goal is personal +messaging. A companion is the role used to take part in conversations; the +other roles operate services for a mesh. + +If you already use MeshCore and want to improve the network, choose by the +result: + +- choose a [repeater](repeater.md) for coverage; +- choose a [room server](room-server.md) for a persistent room; +- choose an [observer](observer.md) for public network data. + +## Before buying or flashing + +Check the detailed hardware and firmware guide linked from the role path. +Support varies by device and firmware target. + + + +## Next step + +Open one role path. If none fits, [ask the community](get-help.md) before +buying hardware. diff --git a/docs/start/companion.md b/docs/start/companion.md new file mode 100644 index 0000000..17af9b3 --- /dev/null +++ b/docs/start/companion.md @@ -0,0 +1,98 @@ +--- +title: Start with a companion +description: Prepare, configure, and verify a personal MeshCore companion for a Canadian community. +audience: + - first-time-user + - companion-owner +task: start-companion +scope: canada-baseline +status: verified +owner: docs-ux +last_reviewed: 2026-07-19 +review_by: 2026-10-19 +tested_with: + content_baseline: f608cfe +difficulty: beginner +estimated_time: 20-30 minutes +destructive: false +requires: + - supported-companion + - data-capable-usb-cable +--- + +# Start with a companion + +Outcome: a personal device that can send an advert and exchange messages on +your local mesh. + +## Understand the role + +A companion is the personal messaging role. It does not route traffic for +other users. Most companions pair with an app; some supported devices include +their own screen and controls. + +## Before you start + +- Check the [recommended companion options](../hardware/recommended-companions.md). +- Confirm the detailed flashing guide lists your device. +- Use a data-capable USB cable and a supported browser. +- Record any identity or settings you need before following an erase step. + +## What this path changes + +The linked flashing guide replaces the device firmware and configures it as a +companion. Stop before erasing if you cannot recover information you need. + +
+

Your setup path

+

Checks are saved only in this browser.

+
    +
  1. +
  2. +
  3. +
  4. +
  5. +
  6. +
  7. +
+
+ +## Flash the companion + +Follow [Flash and configure a companion](../meshcore/flash-companion.md). Use +that guide for device selection, browser connection, flashing, and recovery +steps. + + + +## Apply Canada or local settings + +Use **USA/Canada (Recommended)** with the **3-byte** path setting unless your +local community publishes an override. + +!!! warning "Match your local mesh" + Check the [community directory](../provinces/index.md). If your community + publishes different settings, use those settings instead. + +Reboot after changing the radio settings, then send an advert. + +## Verify success + +Your companion path is complete when: + +1. the device shows the expected Canada or local settings; and +2. a nearby known-good device or community member can see its advert. + +Use the [companion verification checklist](verify.md#companion). + +## Operate and maintain + +Keep the local settings recorded and check them again after firmware changes. +Use [MeshCore how-to guides](../meshcore/general-howto.md) for common messaging +tasks. + +## Next step + +[Find your community](../provinces/index.md), then exchange a test message +with a nearby user. If the advert is not seen, [get help](get-help.md). diff --git a/docs/start/get-help.md b/docs/start/get-help.md new file mode 100644 index 0000000..c410611 --- /dev/null +++ b/docs/start/get-help.md @@ -0,0 +1,60 @@ +--- +title: Get help with a MeshCore setup +description: Choose the shortest support path for a MeshCore device, local settings, observer, or documentation problem. +audience: + - first-time-user + - meshcore-operator +task: get-setup-help +scope: canada-baseline +status: verified +owner: docs-ux +last_reviewed: 2026-07-19 +review_by: 2027-07-19 +tested_with: + content_baseline: f608cfe +difficulty: beginner +estimated_time: 2-5 minutes +destructive: false +--- + +# Get help + +Start with the symptom. Do not post passwords, private keys, precise private +locations, or unredacted command output. + +| What is wrong? | Start here | +|---|---| +| You are unsure which role or hardware fits | [Compare roles](choose-a-goal.md) | +| A nearby device cannot see your advert | Recheck the [verification steps](verify.md) and local settings | +| You need local settings or another person to test | [Find a community](../provinces/index.md) | +| An observer is missing or has no recent activity | [Observer troubleshooting](../analyzer/troubleshooting.md) | +| You need a common MeshCore answer | [MeshCore FAQ](../meshcore/general-faq.md) | +| A page is missing or incorrect | [Share an idea](../submit-idea.md) | + +## Ask the community + +- [MeshCore Canada forum](https://forum.meshcore.ca/) — searchable public + discussions and support. +- [MeshCore Canada Discord](https://discord.gg/BESFVMt7yk) — live community + chat. + +Include: + +- the device role and model; +- the guide and step where you stopped; +- what you expected; +- what happened instead; +- the Canada or local setting name, without secrets; +- any error text after removing sensitive values. + +## Stop before retrying when + +- the next step would erase data you have not backed up; +- the hardware is hot, damaged, wet, or wired unexpectedly; +- you would lose the only way to reach a remote node; +- a local operator says the proposed settings conflict with the mesh. + +## Next step + +Return to [Verify your setup](verify.md) after making one change. Testing one +change at a time makes the result easier to understand. diff --git a/docs/start/index.md b/docs/start/index.md new file mode 100644 index 0000000..3b1234a --- /dev/null +++ b/docs/start/index.md @@ -0,0 +1,95 @@ +--- +title: Start with MeshCore in Canada +description: Choose a MeshCore role and follow a guided setup path using Canada or local network settings. +audience: + - newcomer + - first-time-user +task: start-meshcore +scope: canada-baseline +status: verified +owner: docs-ux +last_reviewed: 2026-07-19 +review_by: 2027-07-19 +tested_with: + content_baseline: f608cfe +difficulty: beginner +estimated_time: 2-5 minutes +destructive: false +--- + +# Start with MeshCore + +Choose what you want the device to do. Each path takes you from preparation +to a clear success check. + +## What do you want to do? + +
+ +- :material-message-text:{ .lg .middle } **Send and receive messages** + + Use a personal companion. + + [:octicons-arrow-right-24: Start the companion path](companion.md) + +- :material-radio-tower:{ .lg .middle } **Improve local coverage** + + Set up a repeater. + + [:octicons-arrow-right-24: Start the repeater path](repeater.md) + +- :material-forum:{ .lg .middle } **Host a persistent room** + + Set up a room server. + + [:octicons-arrow-right-24: Start the room-server path](room-server.md) + +- :material-chart-timeline-variant:{ .lg .middle } **Feed public network tools** + + Set up an observer. + + [:octicons-arrow-right-24: Start the observer path](observer.md) + +- :material-help-circle:{ .lg .middle } **I am not sure** + + Compare what each role does before choosing. + + [:octicons-arrow-right-24: Compare roles](choose-a-goal.md) + +
+ +## Canada baseline + +Start here when your local community does not publish an override. + +| Setting | Canada baseline | +|---|---| +| Radio preset | **USA/Canada (Recommended)** | +| Raw radio values | `910.525 MHz / 62.5 kHz / SF7 / CR5` | +| Path setting | **3-byte** | +| Command-line path setting | `set path.hash.mode 2` | + +!!! warning "Local settings take priority" + Check the [community directory](../provinces/index.md) before configuring + a device. Nearby devices must use matching settings, so follow a published + local override instead of the Canada baseline. + +## What every path includes + +1. Understand the role. +2. Choose compatible hardware. +3. Back up and prepare. +4. Follow the role-specific flashing guide. +5. Apply Canada or local settings. +6. Verify the device nearby. +7. Verify it with your community or the public network tool. +8. Learn how to maintain it and where to get help. + +The role pages can remember which checklist items you complete on this +browser. They store only true/false completion markers. They do not store +credentials, coordinates, private keys, or command output. + +## Next step + +[Compare the roles](choose-a-goal.md) if you are unsure. Otherwise, open the +role path that matches your goal. diff --git a/docs/start/observer.md b/docs/start/observer.md new file mode 100644 index 0000000..fc76fe4 --- /dev/null +++ b/docs/start/observer.md @@ -0,0 +1,107 @@ +--- +title: Start with an observer +description: Choose, configure, and verify a MeshCore observer that sends Canadian network data to public tools. +audience: + - observer-operator + - service-operator +task: start-observer +scope: canada-baseline +status: verified +owner: docs-ux +last_reviewed: 2026-07-19 +review_by: 2026-10-19 +tested_with: + content_baseline: f608cfe +difficulty: intermediate +estimated_time: varies by method +destructive: false +requires: + - supported-observer-method + - internet-connection +--- + +# Start with an observer + +Outcome: an observer that sends received network data to MeshCore Canada's +public tools and appears in the health view. + +## Understand the role + +An observer listens through a MeshCore radio and sends network data to public +services. It does not route mesh traffic. Some methods run on radio firmware; +others use an always-on computer or home-automation system. + +Read [what the service collects and who can access +it](../analyzer/data-collection-access.md) before enabling an observer. + +## Before you start + +- Open the [observer method chooser](../analyzer/intro.md). +- Pick a method that matches the radio, operating system, and host you already + have. +- Back up configuration before replacing firmware or installing a service. +- Use a real nearby location code when the chosen method asks for one. + +## What this path changes + +Depending on the method, setup may replace radio firmware, install a host +service, or add a home-automation integration. It also enables public network +data publishing. + +
+

Your setup path

+

Checks are saved only in this browser.

+
    +
  1. +
  2. +
  3. +
  4. +
  5. +
  6. +
  7. +
+
+ +## Set up the observer + +Use the [observer method chooser](../analyzer/intro.md), then follow only the +guide for the selected method. + + + +## Apply Canada or local settings + +The connected radio must use the same settings as the nearby mesh. Start with +**USA/Canada (Recommended)** and the **3-byte** path setting unless your local community publishes an override. + +!!! warning "Match your local mesh" + Check the [community directory](../provinces/index.md). A connected + observer can be online while hearing no useful traffic if its radio + settings do not match nearby devices. + +The setup method will introduce advanced terms such as **location code** and +the broker connection only when they are needed. + +## Verify success + +Your observer path is complete when: + +1. its radio hears known nearby mesh activity; +2. it appears in [CoreScope Observers](https://live.meshcore.ca/#/observers); + and +3. recent activity appears after a nearby transmission. + +Use the [observer verification checklist](verify.md#observer). + +## Operate and maintain + +Check the observer after radio, network, credential, host, or firmware +changes. Review the [data and access policy](../analyzer/data-collection-access.md) +when service behavior changes. + +## Next step + +Open [Check your observer](../analyzer/verify.md) for the detailed health +view. If it is missing or quiet, [get help](get-help.md). diff --git a/docs/start/repeater.md b/docs/start/repeater.md new file mode 100644 index 0000000..5c01f00 --- /dev/null +++ b/docs/start/repeater.md @@ -0,0 +1,104 @@ +--- +title: Start with a repeater +description: Prepare, configure, commission, and verify a MeshCore repeater for a Canadian community. +audience: + - repeater-builder + - repeater-operator +task: start-repeater +scope: canada-baseline +status: verified +owner: docs-ux +last_reviewed: 2026-07-19 +review_by: 2026-10-19 +tested_with: + content_baseline: f608cfe +difficulty: intermediate +estimated_time: varies by build +destructive: false +requires: + - supported-repeater + - data-capable-usb-cable +--- + +# Start with a repeater + +Outcome: a fixed node that extends local mesh coverage and can be checked +before installation. + +## Understand the role + +A repeater routes mesh traffic for other users. It is fixed infrastructure, +so power, antenna placement, physical access, and maintenance matter as much +as initial configuration. + +## Before you start + +- Check [recommended repeater options](../hardware/recommended-repeaters.md). +- Agree on the site and settings with the local community. +- Keep physical recovery access while testing. +- Record the current identity and settings before any erase or update. + +## What this path changes + +The linked guide installs repeater firmware and changes radio, identity, +administration, advert, and regional settings. Test it on the bench before a +difficult installation. + +
+

Your setup path

+

Checks are saved only in this browser.

+
    +
  1. +
  2. +
  3. +
  4. +
  5. +
  6. +
  7. +
+
+ +## Flash the repeater + +Follow [Flash and configure a repeater](../meshcore/flash-repeater.md). Use the +board-specific decisions, backup guidance, stop conditions, and recovery path +in that guide. + + + +## Apply Canada, local, and regional settings + +Use **USA/Canada (Recommended)** with the **3-byte** path setting unless your +local community publishes an override. + +!!! warning "Coordinate before installation" + Check the [community directory](../provinces/index.md). Nearby repeaters + need matching local settings, and operators should agree on changes that + affect shared traffic. + +Use the [repeater configurator](../config/index.md) to find the regional +settings and review its commands before applying them. + +## Verify success + +Your repeater path is complete when: + +1. it keeps the expected settings after a reboot; +2. it sends an advert; and +3. a nearby known-good companion receives that advert. + +Use the [repeater verification checklist](verify.md#repeater). + +## Operate and maintain + +Keep a record of the site, owner, recovery access, settings, and last +successful check. Recheck the repeater after firmware, antenna, power, or +regional changes. + +## Next step + +Review [antenna](../hardware/recommended-antenna.md) and +[mounting](../hardware/repeater-mounting-options.md) guidance before final +installation. If a check fails, [get help](get-help.md). diff --git a/docs/start/room-server.md b/docs/start/room-server.md new file mode 100644 index 0000000..7a1bb39 --- /dev/null +++ b/docs/start/room-server.md @@ -0,0 +1,97 @@ +--- +title: Start with a room server +description: Prepare, configure, and verify a persistent MeshCore room server for a Canadian community. +audience: + - room-server-operator +task: start-room-server +scope: canada-baseline +status: verified +owner: docs-ux +last_reviewed: 2026-07-19 +review_by: 2026-10-19 +tested_with: + content_baseline: f608cfe +difficulty: intermediate +estimated_time: 20-30 minutes +destructive: false +requires: + - supported-room-server + - data-capable-usb-cable +--- + +# Start with a room server + +Outcome: a persistent room that a companion can discover and enter with the +guest credentials. + +## Understand the role + +A room server keeps a shared room available. It is not a replacement for a +repeater; use a repeater when the goal is routing and coverage. + +## Before you start + +- Confirm the flasher offers room-server firmware for the device. +- Decide who will maintain the room and retain recovery access. +- Record any identity or settings you need before following an erase step. +- Prepare separate guest and administrator credentials. + +## What this path changes + +The linked guide replaces the device firmware and sets the room-server +identity, access credentials, and radio settings. + +
+

Your setup path

+

Checks are saved only in this browser.

+
    +
  1. +
  2. +
  3. +
  4. +
  5. +
  6. +
  7. +
+
+ +## Flash the room server + +Follow [Flash and configure a room server](../meshcore/flash-room-server.md). +Use that guide for device selection, flashing, access setup, and recovery. + + + +## Apply Canada or local settings + +Use **USA/Canada (Recommended)** with the **3-byte** path setting unless your +local community publishes an override. + +!!! warning "Match your local mesh" + Check the [community directory](../provinces/index.md). If your community + publishes different settings, use those settings instead. + +Keep administrator credentials private. Share only the guest access +information intended for room users. + +## Verify success + +Your room-server path is complete when: + +1. a nearby companion discovers its advert; +2. the companion can enter with the guest credentials; and +3. the administrator can still recover and maintain it. + +Use the [room-server verification checklist](verify.md#room-server). + +## Operate and maintain + +Record who maintains the room and how users get support. Recheck discovery and +guest access after firmware, credential, or radio-setting changes. + +## Next step + +Ask a nearby community member to test the room from a second companion. If +discovery or access fails, [get help](get-help.md). diff --git a/docs/start/verify.md b/docs/start/verify.md new file mode 100644 index 0000000..eed8d94 --- /dev/null +++ b/docs/start/verify.md @@ -0,0 +1,88 @@ +--- +title: Verify a MeshCore setup +description: Check whether a companion, repeater, room server, or observer completed its first working task. +audience: + - first-time-user + - meshcore-operator +task: verify-first-success +scope: canada-baseline +status: verified +owner: docs-ux +last_reviewed: 2026-07-19 +review_by: 2026-10-19 +tested_with: + content_baseline: f608cfe +difficulty: beginner +estimated_time: 5-10 minutes +destructive: false +--- + +# Verify your setup + +Do not call setup complete only because a device powers on or a service says +it connected. Use the check for the role you chose. + +Before testing, confirm the device still shows the expected Canada or local +radio settings. + +## Companion + +1. Reboot after the last radio-setting change. +2. Send an advert. +3. Ask a nearby user with a known-good device to confirm that the advert + appears. +4. Exchange a test message when another user is available. + +!!! success "Companion success" + A nearby known-good device sees the advert, and the two devices can + exchange the expected test traffic. + +If the advert is missing, compare radio and path settings before reflashing. + +## Repeater + +1. Keep the repeater accessible on the bench. +2. Reboot it and confirm the intended settings remain. +3. Send an advert from the repeater. +4. Confirm a nearby known-good companion receives it. +5. Repeat the check after any antenna, power, or installation change. + +!!! success "Repeater success" + The repeater retains its settings and a nearby known-good companion + receives its advert. + +Do not install it in a difficult-to-reach location until the bench check +passes. + +## Room server + +1. Reboot the room server. +2. Send its advert. +3. Confirm a nearby companion discovers it. +4. Enter using the guest credentials. +5. Confirm the administrator still has maintenance access. + +!!! success "Room-server success" + A companion discovers the room and guest access works without exposing the + administrator credentials. + +## Observer + +1. Create nearby mesh activity with a known-good device. +2. Confirm the observer radio is using matching settings. +3. Open [CoreScope Observers](https://live.meshcore.ca/#/observers). +4. Find the expected observer and check for recent activity. +5. Use [Check your observer](../analyzer/verify.md) for the full health + checklist. + +!!! success "Observer success" + The expected observer appears in CoreScope and shows recent activity after + a nearby transmission. + +An online broker connection alone is not the final check. + +## Next step + +If the role check passes, return to its page and continue to operation and +maintenance. If it does not pass, use [Get help](get-help.md) and describe +which expected result was missing. diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css index def267d..c9e95be 100644 --- a/docs/stylesheets/extra.css +++ b/docs/stylesheets/extra.css @@ -1,13 +1,8 @@ /* Home page community links use the built-in grid cards styling. */ :root { - --submission-error-color: #b42318; - --submission-error-bg: rgb(180 35 24 / 8%); -} - -[data-md-color-scheme="slate"] { - --submission-error-color: #ffb4ab; - --submission-error-bg: rgb(255 180 171 / 8%); + --submission-error-color: var(--mc-color-danger); + --submission-error-bg: color-mix(in srgb, var(--mc-color-danger) 8%, transparent); } .submission-intro { @@ -26,7 +21,7 @@ padding: 0.75rem 0.9rem; border-left: 3px solid var(--md-default-fg-color--lighter); background: var(--md-code-bg-color); - color: var(--md-default-fg-color--light); + color: var(--mc-color-text); font-size: 0.82rem; } @@ -34,6 +29,56 @@ color: var(--md-default-fg-color); } +.submission-stepper { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 0.5rem; + max-width: 48rem; + margin: 0 0 1.25rem; + padding: 0; + list-style: none; +} + +.submission-stepper li { + display: grid; + grid-template-columns: auto 1fr; + gap: 0.1rem 0.55rem; + align-items: center; + padding: 0.7rem; + border: 1px solid var(--mc-color-border); + border-radius: var(--mc-radius-md); + color: var(--mc-color-text-muted); +} + +.submission-stepper li > span { + display: inline-grid; + grid-row: span 2; + place-items: center; + width: 1.8rem; + height: 1.8rem; + border-radius: 50%; + background: var(--mc-color-surface-raised); + font-weight: 750; +} + +.submission-stepper li[aria-current="step"] { + border-color: var(--mc-color-action); + background: var(--mc-color-surface-raised); + color: var(--mc-color-text); +} + +.submission-stepper li[data-complete="true"] > span { + background: var(--mc-color-success); + color: var(--mc-color-bg); +} + +.submission-stepper small { + color: var(--mc-color-text); + font-size: 0.7rem; + line-height: 1.25; + opacity: 1; +} + .submission-form { display: grid; gap: 1.2rem; @@ -79,6 +124,44 @@ font-weight: 650; } +.submission-error-summary { + padding: 0.8rem 1rem; + border: 2px solid var(--submission-error-color); + border-radius: var(--mc-radius-md); + background: var(--submission-error-bg); +} + +.submission-error-summary h2 { + margin: 0 0 0.4rem; + font-size: 1rem; +} + +.submission-error-summary ul { + margin: 0; +} + +.submission-field-error { + margin: 0; + color: var(--submission-error-color); + font-size: 0.76rem; +} + +.submission-form [aria-invalid="true"] { + border-color: var(--submission-error-color); +} + +.submission-character-count { + justify-self: end; + margin: -0.15rem 0 0; + color: var(--mc-color-text-muted); + font-size: 0.7rem; +} + +.submission-character-count[data-near-limit="true"] { + color: var(--submission-error-color); + font-weight: 700; +} + .submission-no-script, .submission-github-note { margin: 0; @@ -172,6 +255,20 @@ font-size: 0.84rem; } +.submission-draft-actions { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.6rem; +} + +.submission-draft-actions p { + flex-basis: 100%; + margin: 0; + color: var(--mc-color-text-muted); + font-size: 0.76rem; +} + .submission-consent input { width: auto; min-height: auto; @@ -202,6 +299,10 @@ gap: 0.6rem; } +.submission-public-summary { + flex-basis: 100%; +} + .submission-review-action .md-button, .submission-actions .md-button { margin: 0; @@ -289,6 +390,12 @@ overflow-wrap: anywhere; } +.submission-preview-note { + margin: -0.8rem 0 0; + color: var(--mc-color-text-muted); + font-size: 0.78rem; +} + .submission-alternatives { margin: 1.25rem 0 2rem; padding: 0.8rem 0; @@ -303,6 +410,14 @@ } @media screen and (max-width: 52em) { + .submission-stepper { + grid-template-columns: 1fr; + } + + .submission-stepper small { + display: block; + } + .submission-form__grid { grid-template-columns: 1fr; } diff --git a/docs/submit-idea.md b/docs/submit-idea.md index c1eb6b8..1f39a00 100644 --- a/docs/submit-idea.md +++ b/docs/submit-idea.md @@ -1,4 +1,22 @@ --- +title: Share an idea +description: Send a public idea or problem to MeshCore Canada without needing a GitHub account. +audience: + - community-member +task: share-an-idea +scope: canada-baseline +status: verified +owner: maintainers-gateway +last_reviewed: 2026-07-19 +review_by: 2026-10-19 +tested_with: + submission_gateway: v1 +difficulty: beginner +estimated_time: 3-5 minutes +page_styles: + - stylesheets/extra.css +page_scripts: + - javascripts/submission-form.js hide: - toc --- @@ -9,6 +27,18 @@ hide:

Public form. Do not include passwords, keys, addresses, or private coordinates.

+
    +
  1. + 1ReviewCheck what will be public +
  2. +
  3. + 2VerifyComplete the anti-spam check +
  4. +
  5. + 3SubmitCreate the public issue +
  6. +
+
@@ -17,6 +47,10 @@ hide: JavaScript is off. Continue on GitHub (account required). +

Describe your idea

@@ -95,6 +129,12 @@ hide: This can be posted publicly and contains no private information. +
+ + +

Confirm the public-content statement before saving a draft.

+
+ + -
+
diff --git a/mkdocs.yml b/mkdocs.yml index c378791..aa0a82b 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,27 +1,65 @@ site_name: MeshCore Canada site_url: https://meshcore.ca +site_description: Practical, community-reviewed guidance for joining and operating MeshCore networks in Canada. repo_name: MeshCore Canada repo_url: https://github.com/MeshCore-ca/MeshCore-Canada +edit_uri: edit/main/docs/ +use_directory_urls: true +copyright: Community-run documentation for MeshCore users in Canada. Not affiliated with the upstream MeshCore project. theme: name: material - favicon: assets/MeshCore-Canada-Favicon.png # optional - logo: assets/MeshCore-Canada-Favicon.png # optional + custom_dir: overrides + language: en + favicon: assets/MeshCore-Canada-Favicon.png + logo: assets/MeshCore-Canada-Favicon.png + font: false palette: - scheme: slate - primary: indigo - accent: blue + - scheme: slate + primary: custom + accent: custom + toggle: + icon: material/brightness-7 + name: Use light theme + - scheme: default + primary: custom + accent: custom + toggle: + icon: material/brightness-4 + name: Use dark theme features: + - navigation.tabs + - navigation.tabs.sticky + - navigation.sections + - navigation.path + - navigation.footer - navigation.top + - search.suggest + - search.highlight + - content.code.copy + - content.action.edit - toc.follow extra_css: - - stylesheets/extra.css - - assets/regions/regions.css + - assets/styles/tokens.css + - assets/styles/base.css + - assets/styles/shell.css + - assets/styles/components.css extra_javascript: - - assets/regions/regions.js - - javascripts/submission-form.js + - assets/javascripts/bootstrap.js?v=20260719-2 + +extra: + social: + - icon: fontawesome/solid/comments + link: https://forum.meshcore.ca/ + name: MeshCore Canada forum + - icon: material/access-point-network + link: https://live.meshcore.ca/ + name: Live MeshCore Canada network tools + - icon: fontawesome/brands/github + link: https://github.com/MeshCore-ca/MeshCore-Canada + name: MeshCore Canada on GitHub plugins: - search @@ -48,65 +86,77 @@ markdown_extensions: permalink: true nav: - - Welcome: index.md - - Analyzer & MQTT: - - Overview: analyzer/intro.md - - Data Collection & Access: analyzer/data-collection-access.md - - Check Your Observer: analyzer/verify.md - - Troubleshooting: analyzer/troubleshooting.md - - Broker Reference: analyzer/broker-reference.md - - IATA Region Codes: analyzer/iata-codes.md - - RemoteTerm: analyzer/remoteterm.md - - Build Guides: - - MCtoMQTT Script: analyzer/builds/mctomqtt.md - - MeshCore-HA: analyzer/builds/meshcore-ha.md - - MQTT Firmware: analyzer/builds/mqtt-firmware.md + - Start: + - Start here: start/index.md + - Choose a role: start/choose-a-goal.md + - Companion: start/companion.md + - Repeater: start/repeater.md + - Room server: start/room-server.md + - Observer: start/observer.md + - Finish and support: + - Verify your setup: start/verify.md + - Get help: start/get-help.md + - Communities: + - Find a community: provinces/index.md + - Western Canada: + - British Columbia: provinces/british-columbia.md + - Alberta: provinces/alberta.md + - Saskatchewan: provinces/saskatchewan.md + - Manitoba: provinces/manitoba.md + - Central Canada: + - Ontario: provinces/ontario.md + - Quebec: provinces/quebec.md + - Atlantic Canada: + - New Brunswick: provinces/new-brunswick.md + - Nova Scotia: provinces/nova-scotia.md + - Prince Edward Island: provinces/prince-edward-island.md + - Newfoundland and Labrador: provinces/newfoundland-and-labrador.md + - Territories: provinces/territories.md + - Devices & Builds: + - Choose hardware: hardware/overview.md + - Companion devices: hardware/recommended-companions.md + - Repeater devices: hardware/recommended-repeaters.md + - Antennas: hardware/recommended-antenna.md + - Solar repeater builds: + - Build a 300 mW repeater: hardware/repeater-solar-300mw-diy-build.md + - Build a 1 W repeater: hardware/repeater-solar-1w-diy-build.md + - Install a repeater: + - Mounting options: hardware/repeater-mounting-options.md + - Firmware: + - Flash a companion: meshcore/flash-companion.md + - Flash a repeater: meshcore/flash-repeater.md + - Flash a room server: meshcore/flash-room-server.md + - Update a repeater over the air: meshcore/update-repeater-ota.md + - Generate a repeater identity: meshcore/generate-repeater-id.md + - Network Tools: + - Configure a repeater: config/index.md + - Explore the region map: config/map.md + - Set up an observer: analyzer/intro.md + - Check an observer: analyzer/verify.md + - Troubleshoot network data: analyzer/troubleshooting.md + - Observer methods: + - Compare collection methods: analyzer/data-collection-access.md + - RemoteTerm: analyzer/remoteterm.md + - MCtoMQTT: analyzer/builds/mctomqtt.md + - Home Assistant: analyzer/builds/meshcore-ha.md + - MQTT firmware: analyzer/builds/mqtt-firmware.md - PyMC: analyzer/builds/pymc.md - - Hardware: - - Overview: hardware/overview.md - - Antenna: hardware/recommended-antenna.md - - Companion Nodes: hardware/recommended-companions.md - - Repeater Nodes: - - Repeater Nodes: hardware/recommended-repeaters.md - - Repeater Mounting Options: hardware/repeater-mounting-options.md - - Repeater Solar Batteries: hardware/repeater-solar-batteries.md - - 300mW Solar Repeater Build Guide: hardware/repeater-solar-300mw-diy-build.md - - 1W Solar Repeater Build Guide: hardware/repeater-solar-1w-diy-build.md - - Wire Connector Types: hardware/wire-connector-types.md - - - MeshCore: - - General: - - Overview: meshcore/general-overview.md - - MeshCore FAQ: meshcore/general-faq.md - - MeshCore How-To: meshcore/general-howto.md - - MeshCore Roles: meshcore/general-meshcore-roles.md - - Repeater Configurator: /config/ - - Firmware Guides: - - Flashing a Companion: meshcore/flash-companion.md - - Flashing a Repeater: meshcore/flash-repeater.md - - Updating a Repeater (OTA): meshcore/update-repeater-ota.md - - Generating a Repeater ID: meshcore/generate-repeater-id.md - - Flashing a Room Server: meshcore/flash-room-server.md - - RAK4631 Custom Display Firmware: meshcore/firmware-rak-custom-display.md - - Heltec V3 Wi-Fi Firmware: meshcore/firmware-heltec-v3-wifi.md - - - Mesh Directory: - - Overview: provinces/index.md - - British Columbia: provinces/british-columbia.md - - Alberta: provinces/alberta.md - - Saskatchewan: provinces/saskatchewan.md - - Manitoba: provinces/manitoba.md - - Ontario: provinces/ontario.md - - Quebec: provinces/quebec.md - - New Brunswick: provinces/new-brunswick.md - - Nova Scotia: provinces/nova-scotia.md - - Prince Edward Island: provinces/prince-edward-island.md - - Newfoundland and Labrador: provinces/newfoundland-and-labrador.md - - Territories (YT / NT / NU): provinces/territories.md - - Resources: - - Getting Started: resources/getting-started.md - - Useful Links: resources/links.md + - References: + - Broker reference: analyzer/broker-reference.md + - Location codes: analyzer/iata-codes.md + - Learn: + - What MeshCore is: meshcore/general-overview.md + - Node roles: meshcore/general-meshcore-roles.md + - How to use MeshCore: meshcore/general-howto.md + - Frequently asked questions: meshcore/general-faq.md + - Canadian region standard: config/standard.md - Glossary: resources/glossary.md - - Share an Idea: submit-idea.md - - Forum: https://forum.meshcore.ca/ - - Contributing: contributing.md + - Useful links: resources/links.md + - Contribute: + - Ways to contribute: contributing.md + - Share an idea: submit-idea.md + - Propose a region change: config/map.md + - Privacy: privacy.md + - Community forum: https://forum.meshcore.ca/ + - Community Discord: https://discord.gg/BESFVMt7yk + - GitHub repository: https://github.com/MeshCore-ca/MeshCore-Canada diff --git a/overrides/404.html b/overrides/404.html new file mode 100644 index 0000000..e49fdf0 --- /dev/null +++ b/overrides/404.html @@ -0,0 +1,22 @@ +{% extends "main.html" %} + +{% block content %} +

That link did not land

+

Page not found

+

The page may have moved. Choose a useful next step.

+ +

+{% endblock %} diff --git a/overrides/main.html b/overrides/main.html new file mode 100644 index 0000000..97ed50f --- /dev/null +++ b/overrides/main.html @@ -0,0 +1,46 @@ +{% extends "base.html" %} + +{% block extrahead %} + {{ super() }} + {% if page and page.meta %} + {% if page.meta.status in ["draft", "experimental", "legacy", "archived"] %} + + {% endif %} + + + + + + + + + {% for stylesheet in page.meta.page_styles or [] %} + + {% endfor %} + {% endif %} +{% endblock %} + +{% block scripts %} + {{ super() }} + {% if page and page.meta %} + {% for script in page.meta.page_scripts or [] %} + + {% endfor %} + {% for module in page.meta.page_modules or [] %} + + {% endfor %} + {% endif %} +{% endblock %} diff --git a/overrides/partials/content.html b/overrides/partials/content.html new file mode 100644 index 0000000..166e0e5 --- /dev/null +++ b/overrides/partials/content.html @@ -0,0 +1,18 @@ +{% include "partials/tags.html" %} +{% include "partials/actions.html" %} + +{% if "\u003ch1" not in page.content %} +

{{ page.title | d(config.site_name, true) }}

+ {% include "partials/page-lifecycle.html" %} + {{ page.content }} +{% else %} + {% set content_parts = page.content.split("", 1) %} + {{ content_parts[0] }} + {% include "partials/page-lifecycle.html" %} + {{ content_parts[1] }} +{% endif %} + +{% include "partials/source-file.html" %} +{% include "partials/page-feedback.html" %} +{% include "partials/feedback.html" %} +{% include "partials/comments.html" %} diff --git a/overrides/partials/page-feedback.html b/overrides/partials/page-feedback.html new file mode 100644 index 0000000..1ab2f46 --- /dev/null +++ b/overrides/partials/page-feedback.html @@ -0,0 +1,6 @@ +{% if page and page.meta and not page.meta.hide_feedback %} + +{% endif %} diff --git a/overrides/partials/page-lifecycle.html b/overrides/partials/page-lifecycle.html new file mode 100644 index 0000000..ca0e540 --- /dev/null +++ b/overrides/partials/page-lifecycle.html @@ -0,0 +1,13 @@ +{% if page and page.meta %} + {% set status = page.meta.status %} + {% if status in ["draft", "experimental", "legacy", "archived"] %} + + {% endif %} + +{% endif %} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..8df43b4 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1794 @@ +{ + "name": "meshcore-canada-docs", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "meshcore-canada-docs", + "version": "1.0.0", + "devDependencies": { + "@axe-core/playwright": "4.12.1", + "@playwright/test": "1.61.1", + "chrome-launcher": "1.2.1", + "lighthouse": "12.6.1" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@axe-core/playwright": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@axe-core/playwright/-/playwright-4.12.1.tgz", + "integrity": "sha512-rMd7xriptqKpP+w5265i4Hdkv2X5kbu6uiBi/B2I7uf3hieRBM3qDCfaKPtxfiYb2mKXfF+yLODJwIx+Jv1GDw==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "axe-core": "~4.12.1" + }, + "peerDependencies": { + "playwright-core": ">= 1.0.0" + } + }, + "node_modules/@formatjs/ecma402-abstract": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.6.tgz", + "integrity": "sha512-HJnTFeRM2kVFVr5gr5kH1XP6K0JcJtE7Lzvtr3FS/so5f1kpsqqqxy5JF+FRaO6H2qmcMfAUIox7AJteieRtVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@formatjs/fast-memoize": "2.2.7", + "@formatjs/intl-localematcher": "0.6.2", + "decimal.js": "^10.4.3", + "tslib": "^2.8.0" + } + }, + "node_modules/@formatjs/fast-memoize": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.7.tgz", + "integrity": "sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@formatjs/icu-messageformat-parser": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.11.4.tgz", + "integrity": "sha512-7kR78cRrPNB4fjGFZg3Rmj5aah8rQj9KPzuLsmcSn4ipLXQvC04keycTI1F7kJYDwIXtT2+7IDEto842CfZBtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@formatjs/ecma402-abstract": "2.3.6", + "@formatjs/icu-skeleton-parser": "1.8.16", + "tslib": "^2.8.0" + } + }, + "node_modules/@formatjs/icu-skeleton-parser": { + "version": "1.8.16", + "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.16.tgz", + "integrity": "sha512-H13E9Xl+PxBd8D5/6TVUluSpxGNvFSlN/b3coUp0e0JpuWXXnQDiavIpY3NnvSp4xhEMoXyyBvVfdFX8jglOHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@formatjs/ecma402-abstract": "2.3.6", + "tslib": "^2.8.0" + } + }, + "node_modules/@formatjs/intl-localematcher": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.6.2.tgz", + "integrity": "sha512-XOMO2Hupl0wdd172Y06h6kLpBz6Dv+J4okPLl4LPtzbr8f66WbIoy4ev98EBuZ6ZK4h5ydTN6XneT4QVpD7cdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@paulirish/trace_engine": { + "version": "0.0.53", + "resolved": "https://registry.npmjs.org/@paulirish/trace_engine/-/trace_engine-0.0.53.tgz", + "integrity": "sha512-PUl/vlfo08Oj804VI5nDPeSk9vyslnBlVzDDwFt8SUVxY8+KdGMkra/vrXjEEHe8gb7+RqVTfOIlGw0nyrEelA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "legacy-javascript": "latest", + "third-party-web": "latest" + } + }, + "node_modules/@playwright/test": { + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.61.1.tgz", + "integrity": "sha512-8nKv6+0RJSL9FE4jYOEGXnPeM/Hg12qZpmqzZjRh3qM0Y7c3z1mrOTfFLids72RDQYVh9WpLEfR5WdpNX4fkig==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.61.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@puppeteer/browsers": { + "version": "2.13.2", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.13.2.tgz", + "integrity": "sha512-5EUZSUIc37H6aIXyWO0Z4y8NlF8NnjgmqeQgOGiswAU7pY0HOo16ho4+alIWmSfdZnjqBRawMsP3I5YqLSn6kw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.4.3", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.5.0", + "semver": "^7.7.4", + "tar-fs": "^3.1.1", + "yargs": "^17.7.2" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@puppeteer/browsers/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@sentry-internal/tracing": { + "version": "7.120.4", + "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.120.4.tgz", + "integrity": "sha512-Fz5+4XCg3akeoFK+K7g+d7HqGMjmnLoY2eJlpONJmaeT9pXY7yfUyXKZMmMajdE2LxxKJgQ2YKvSCaGVamTjHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sentry/core": "7.120.4", + "@sentry/types": "7.120.4", + "@sentry/utils": "7.120.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/core": { + "version": "7.120.4", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.120.4.tgz", + "integrity": "sha512-TXu3Q5kKiq8db9OXGkWyXUbIxMMuttB5vJ031yolOl5T/B69JRyAoKuojLBjRv1XX583gS1rSSoX8YXX7ATFGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sentry/types": "7.120.4", + "@sentry/utils": "7.120.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/integrations": { + "version": "7.120.4", + "resolved": "https://registry.npmjs.org/@sentry/integrations/-/integrations-7.120.4.tgz", + "integrity": "sha512-kkBTLk053XlhDCg7OkBQTIMF4puqFibeRO3E3YiVc4PGLnocXMaVpOSCkMqAc1k1kZ09UgGi8DxfQhnFEjUkpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sentry/core": "7.120.4", + "@sentry/types": "7.120.4", + "@sentry/utils": "7.120.4", + "localforage": "^1.8.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/node": { + "version": "7.120.4", + "resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.120.4.tgz", + "integrity": "sha512-qq3wZAXXj2SRWhqErnGCSJKUhPSlZ+RGnCZjhfjHpP49KNpcd9YdPTIUsFMgeyjdh6Ew6aVCv23g1hTP0CHpYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sentry-internal/tracing": "7.120.4", + "@sentry/core": "7.120.4", + "@sentry/integrations": "7.120.4", + "@sentry/types": "7.120.4", + "@sentry/utils": "7.120.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/types": { + "version": "7.120.4", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.120.4.tgz", + "integrity": "sha512-cUq2hSSe6/qrU6oZsEP4InMI5VVdD86aypE+ENrQ6eZEVLTCYm1w6XhW1NvIu3UuWh7gZec4a9J7AFpYxki88Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/utils": { + "version": "7.120.4", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.120.4.tgz", + "integrity": "sha512-zCKpyDIWKHwtervNK2ZlaK8mMV7gVUijAgFeJStH+CU/imcdquizV3pFLlSQYRswG+Lbyd6CT/LGRh3IbtkCFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sentry/types": "7.120.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "26.1.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.1.tgz", + "integrity": "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~8.3.0" + } + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/axe-core": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.12.1.tgz", + "integrity": "sha512-s7iGf5GaVMxEG0ENN9x+xTr7GFZCb1ZP/1uATUpCEK2X78nDB3RwbtFCo9pGAf9ru+VwoQ464DkaLEeRM08wJA==", + "dev": true, + "license": "MPL-2.0", + "engines": { + "node": ">=4" + } + }, + "node_modules/b4a": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz", + "integrity": "sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, + "node_modules/bare-events": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.9.1.tgz", + "integrity": "sha512-Z0oHEHAFDZkffN8Qc39zNZjQlMDkPJRyyyZieU1VH7u8c5S+qHZ2S8ixdKIAxEjfHO7FJxXmJWgteOghVanIsg==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "bare-abort-controller": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + } + } + }, + "node_modules/bare-fs": { + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.7.4.tgz", + "integrity": "sha512-y1kC+ffIx/tPLdTE693uNjHfzTfr+ravR5tvWlMXe25nELbkqV400S71qHDwbkAQ1FVEZobB1NFRzFbCCcyBCQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.5.4", + "bare-path": "^3.0.0", + "bare-stream": "^2.6.4", + "bare-url": "^2.2.2", + "fast-fifo": "^1.3.2" + }, + "engines": { + "bare": ">=1.16.0" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } + } + }, + "node_modules/bare-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.1.1.tgz", + "integrity": "sha512-JprUlveX3QjApC1cTpsUOiscADftCGVWkzitbHsRqv84hzYwYHw2mbluddsq5TvI8mH/8Ov1f4BiMAdcB0oYnQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/bare-stream": { + "version": "2.13.3", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.3.tgz", + "integrity": "sha512-Kc+brLqvEqGkjyfiwJmImAOqLZL7OsoLKuavx+hJjgVV3nLTOjloJyPMFxjUPerGGHrNH0fLU06jjykMLWrERQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.8.1", + "streamx": "^2.25.0", + "teex": "^1.0.1" + }, + "peerDependencies": { + "bare-abort-controller": "*", + "bare-buffer": "*", + "bare-events": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + }, + "bare-buffer": { + "optional": true + }, + "bare-events": { + "optional": true + } + } + }, + "node_modules/bare-url": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.5.tgz", + "integrity": "sha512-K+y9xF1tN+CdPu4qWwr0QiK1Al07eFPGYK5M2pDXcmHdMdgC/tT/bpmMe1hrmRHaidKLkXrC+cRNYf3XVDUhSQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bare-path": "^3.0.0" + } + }, + "node_modules/basic-ftp": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.3.1.tgz", + "integrity": "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/chrome-launcher": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.2.1.tgz", + "integrity": "sha512-qmFR5PLMzHyuNJHwOloHPAHhbaNglkfeV/xDtt5b7xiFFyU1I+AZZX0PYseMuhenJSSirgxELYIbswcoc+5H4A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/node": "*", + "escape-string-regexp": "^4.0.0", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^2.0.1" + }, + "bin": { + "print-chrome-path": "bin/print-chrome-path.cjs" + }, + "engines": { + "node": ">=12.13.0" + } + }, + "node_modules/chromium-bidi": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-14.0.0.tgz", + "integrity": "sha512-9gYlLtS6tStdRWzrtXaTMnqcM4dudNegMXJxkR0I/CXObHalYeYcAMPrL19eroNZHtJ8DQmu1E+ZNOYu/IXMXw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "mitt": "^3.0.1", + "zod": "^3.24.1" + }, + "peerDependencies": { + "devtools-protocol": "*" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/configstore": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", + "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dot-prop": "^5.2.0", + "graceful-fs": "^4.1.2", + "make-dir": "^3.0.0", + "unique-string": "^2.0.0", + "write-file-atomic": "^3.0.0", + "xdg-basedir": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/csp_evaluator": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/csp_evaluator/-/csp_evaluator-1.1.5.tgz", + "integrity": "sha512-EL/iN9etCTzw/fBnp0/uj0f5BOOGvZut2mzsiiBZ/FdT6gFQCKRO/tmcKOxn5drWZ2Ndm/xBb1SI4zwWbGtmIw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/devtools-protocol": { + "version": "0.0.1467305", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1467305.tgz", + "integrity": "sha512-LxwMLqBoPPGpMdRL4NkLFRNy3QLp6Uqa7GNp1v6JaBheop2QrB9Q7q0A/q/CYYP9sBfZdHOyszVx4gc9zyk7ow==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/enquirer": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", + "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-colors": "^4.1.1", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/events-universal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", + "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.7.0" + } + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-uri": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz", + "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/http-link-header": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/http-link-header/-/http-link-header-1.1.4.tgz", + "integrity": "sha512-xT3GPW6/ZbGuw4UvwHqErSCEjNUlwbQJuZn9/q5U4WEKfp2kENVCAlousG1zLxHeaQ/ffOHUNpWamvkbBW0eNw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/image-ssim": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/image-ssim/-/image-ssim-0.2.0.tgz", + "integrity": "sha512-W7+sO6/yhxy83L0G7xR8YAc5Z5QFtYEXXRV6EaE8tuYBZJnA3gVgp3q7X7muhLZVodeb9UfvjSbwt9VJwjIYAg==", + "dev": true, + "license": "MIT" + }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/intl-messageformat": { + "version": "10.7.18", + "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.7.18.tgz", + "integrity": "sha512-m3Ofv/X/tV8Y3tHXLohcuVuhWKo7BBq62cqY15etqmLxg2DZ34AGGgQDeR+SCta2+zICb1NX83af0GJmbQ1++g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@formatjs/ecma402-abstract": "2.3.6", + "@formatjs/fast-memoize": "2.2.7", + "@formatjs/icu-messageformat-parser": "2.11.4", + "tslib": "^2.8.0" + } + }, + "node_modules/ip-address": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", + "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jpeg-js": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.4.tgz", + "integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/js-library-detector": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/js-library-detector/-/js-library-detector-6.7.0.tgz", + "integrity": "sha512-c80Qupofp43y4cJ7+8TTDN/AsDwLi5oOm/plBrWI+iQt485vKXCco+yVmOwEgdo9VOdsYTuV0UlTeetVPTriXA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/legacy-javascript": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/legacy-javascript/-/legacy-javascript-0.0.1.tgz", + "integrity": "sha512-lPyntS4/aS7jpuvOlitZDFifBCb4W8L/3QU0PLbUTUj+zYah8rfVjYic88yG7ZKTxhS5h9iz7duT8oUXKszLhg==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/lie": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", + "integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==", + "dev": true, + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/lighthouse": { + "version": "12.6.1", + "resolved": "https://registry.npmjs.org/lighthouse/-/lighthouse-12.6.1.tgz", + "integrity": "sha512-85WDkjcXAVdlFem9Y6SSxqoKiz/89UsDZhLpeLJIsJ4LlHxw047XTZhlFJmjYCB7K5S1erSBAf5cYLcfyNbH3A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@paulirish/trace_engine": "0.0.53", + "@sentry/node": "^7.0.0", + "axe-core": "^4.10.3", + "chrome-launcher": "^1.2.0", + "configstore": "^5.0.1", + "csp_evaluator": "1.1.5", + "devtools-protocol": "0.0.1467305", + "enquirer": "^2.3.6", + "http-link-header": "^1.1.1", + "intl-messageformat": "^10.5.3", + "jpeg-js": "^0.4.4", + "js-library-detector": "^6.7.0", + "lighthouse-logger": "^2.0.1", + "lighthouse-stack-packs": "1.12.2", + "lodash-es": "^4.17.21", + "lookup-closest-locale": "6.2.0", + "metaviewport-parser": "0.3.0", + "open": "^8.4.0", + "parse-cache-control": "1.0.1", + "puppeteer-core": "^24.10.0", + "robots-parser": "^3.0.1", + "semver": "^5.3.0", + "speedline-core": "^1.4.3", + "third-party-web": "^0.26.6", + "tldts-icann": "^6.1.16", + "ws": "^7.0.0", + "yargs": "^17.3.1", + "yargs-parser": "^21.0.0" + }, + "bin": { + "chrome-debug": "core/scripts/manual-chrome-launcher.js", + "lighthouse": "cli/index.js", + "smokehouse": "cli/test/smokehouse/frontends/smokehouse-bin.js" + }, + "engines": { + "node": ">=18.20" + } + }, + "node_modules/lighthouse-logger": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-2.0.2.tgz", + "integrity": "sha512-vWl2+u5jgOQuZR55Z1WM0XDdrJT6mzMP8zHUct7xTlWhuQs+eV0g+QL0RQdFjT54zVmbhLCP8vIVpy1wGn/gCg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.4.1", + "marky": "^1.2.2" + } + }, + "node_modules/lighthouse-stack-packs": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/lighthouse-stack-packs/-/lighthouse-stack-packs-1.12.2.tgz", + "integrity": "sha512-Ug8feS/A+92TMTCK6yHYLwaFMuelK/hAKRMdldYkMNwv+d9PtWxjXEg6rwKtsUXTADajhdrhXyuNCJ5/sfmPFw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/localforage": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz", + "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "lie": "3.1.1" + } + }, + "node_modules/lodash-es": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz", + "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==", + "dev": true, + "license": "MIT" + }, + "node_modules/lookup-closest-locale": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/lookup-closest-locale/-/lookup-closest-locale-6.2.0.tgz", + "integrity": "sha512-/c2kL+Vnp1jnV6K6RpDTHK3dgg0Tu2VVp+elEiJpjfS1UyY7AjOYHohRug6wT0OpoX2qFgNORndE9RqesfVxWQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/marky": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/marky/-/marky-1.3.0.tgz", + "integrity": "sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/metaviewport-parser": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/metaviewport-parser/-/metaviewport-parser-0.3.0.tgz", + "integrity": "sha512-EoYJ8xfjQ6kpe9VbVHvZTZHiOl4HL1Z18CrZ+qahvLXT7ZO4YTC2JMyt5FaUp9JJp6J4Ybb/z7IsCXZt86/QkQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "dev": true, + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/netmask": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.1.1.tgz", + "integrity": "sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pac-proxy-agent": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", + "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.6", + "pac-resolver": "^7.0.1", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-resolver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "dev": true, + "license": "MIT", + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/parse-cache-control": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-cache-control/-/parse-cache-control-1.0.1.tgz", + "integrity": "sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg==", + "dev": true + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT" + }, + "node_modules/playwright": { + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.61.1.tgz", + "integrity": "sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.61.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.61.1.tgz", + "integrity": "sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/proxy-agent": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", + "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.6", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.1.0", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true, + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/puppeteer-core": { + "version": "24.43.1", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.43.1.tgz", + "integrity": "sha512-T5ScUMAsmhdNbgDR41AGESYeS6V9MSgetkSnVhhW+gXvzC42VesKCn5ld87gAZDJ6vLHL9GkRvY9WtQWSnwFbw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@puppeteer/browsers": "2.13.2", + "chromium-bidi": "14.0.0", + "debug": "^4.4.3", + "devtools-protocol": "0.0.1608973", + "typed-query-selector": "^2.12.2", + "webdriver-bidi-protocol": "0.4.1", + "ws": "^8.20.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/puppeteer-core/node_modules/devtools-protocol": { + "version": "0.0.1608973", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1608973.tgz", + "integrity": "sha512-Tpm17fxYzt+J7VrGdc1k8YdRqS3YV7se/M6KeemEqvUbq/n7At1rWVuXMxQgpWkdwSdIEKYbU//Bve+Shm4YNQ==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/puppeteer-core/node_modules/ws": { + "version": "8.21.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz", + "integrity": "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/robots-parser": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/robots-parser/-/robots-parser-3.0.1.tgz", + "integrity": "sha512-s+pyvQeIKIZ0dx5iJiQk1tPLJAWln39+MI5jtM8wnyws+G5azk+dMnMX0qfbqNetKKNgcWWOdi0sfm+FbQbgdQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.9.tgz", + "integrity": "sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ip-address": "^10.1.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/speedline-core": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/speedline-core/-/speedline-core-1.4.3.tgz", + "integrity": "sha512-DI7/OuAUD+GMpR6dmu8lliO2Wg5zfeh+/xsdyJZCzd8o5JgFUjCeLsBDuZjIQJdwXS3J0L/uZYrELKYqx+PXog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "image-ssim": "^0.2.0", + "jpeg-js": "^0.4.1" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/streamx": { + "version": "2.28.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.28.0.tgz", + "integrity": "sha512-1Yowhzjf0ivGMrTIkY9hav5TxobO9qIVqUE41fiCGMGgc3CLlf4MY+9AHmZqBWgDTue0fY9zWjYFVyf6Diuobw==", + "dev": true, + "license": "MIT", + "dependencies": { + "events-universal": "^1.0.0", + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar-fs": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.3.tgz", + "integrity": "sha512-/hU4AXnIdZu+Gvl1pk0oI5f5HxWsCJRtY2aFaJdk9VvyL48DWU6iU5WAIPG+wIi1YvWA6eTJvIviP/tMAZZNwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^4.0.1", + "bare-path": "^3.0.0" + } + }, + "node_modules/tar-stream": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.2.0.tgz", + "integrity": "sha512-ojzvCvVaNp6aOTFmG7jaRD0meowIAuPc3cMMhSgKiVWws1GyHbGd/xvnyuRKcKlMpt3qvxx6r0hreCNITP9hIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "bare-fs": "^4.5.5", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/teex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", + "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "streamx": "^2.12.5" + } + }, + "node_modules/text-decoder": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz", + "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, + "node_modules/third-party-web": { + "version": "0.26.7", + "resolved": "https://registry.npmjs.org/third-party-web/-/third-party-web-0.26.7.tgz", + "integrity": "sha512-buUzX4sXC4efFX6xg2bw6/eZsCUh8qQwSavC4D9HpONMFlRbcHhD8Je5qwYdCpViR6q0qla2wPP+t91a2vgolg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tldts-core": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tldts-icann": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts-icann/-/tldts-icann-6.1.86.tgz", + "integrity": "sha512-NFxmRT2lAEMcCOBgeZ0NuM0zsK/xgmNajnY6n4S1mwAKocft2s2ise1O3nQxrH3c+uY6hgHUV9GGNVp7tUE4Sg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^6.1.86" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/typed-query-selector": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.2.tgz", + "integrity": "sha512-EOPFbyIub4ngnEdqi2yOcNeDLaX/0jcE1JoAXQDDMIthap7FoN795lc/SHfIq2d416VufXpM8z/lD+WRm2gfOQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/undici-types": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", + "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "crypto-random-string": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/webdriver-bidi-protocol": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/webdriver-bidi-protocol/-/webdriver-bidi-protocol-0.4.1.tgz", + "integrity": "sha512-ARrjNjtWRRs2w4Tk7nqrf2gBI0QXWuOmMCx2hU+1jUt6d00MjMxURrhxhGbrsoiZKJrhTSTzbIrc554iKI10qw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "node_modules/ws": { + "version": "7.5.13", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.13.tgz", + "integrity": "sha512-rsKI6xDBFVf4r/x8XyChGK04QR/XHroxs/jUcoWvtEZM8TPU/X/uIY9B1CsSzYws9ZJb/6bbBu7dPhFW00CAoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xdg-basedir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", + "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..8e4f064 --- /dev/null +++ b/package.json @@ -0,0 +1,25 @@ +{ + "name": "meshcore-canada-docs", + "version": "1.0.0", + "private": true, + "type": "module", + "engines": { + "node": ">=22" + }, + "scripts": { + "docs:build": "python -m mkdocs build --strict --site-dir .tmp/site && node scripts/postprocess-site.mjs", + "check:links": "npm run docs:build && node scripts/check-built-links.mjs", + "test:content": "node --test tests/content/*.test.mjs", + "test:editor": "node --test tests/editor/*.test.mjs", + "test:browser": "playwright test", + "test:browser:chromium": "playwright test --project=chromium", + "test:a11y": "playwright test tests/browser/accessibility.spec.mjs --project=chromium", + "audit:lighthouse": "npm run docs:build && node scripts/run-lighthouse.mjs" + }, + "devDependencies": { + "@axe-core/playwright": "4.12.1", + "@playwright/test": "1.61.1", + "chrome-launcher": "1.2.1", + "lighthouse": "12.6.1" + } +} diff --git a/playwright.config.mjs b/playwright.config.mjs new file mode 100644 index 0000000..cef30d0 --- /dev/null +++ b/playwright.config.mjs @@ -0,0 +1,62 @@ +import { defineConfig, devices } from "@playwright/test"; + +const externalBaseUrl = process.env.PLAYWRIGHT_BASE_URL; +const baseURL = externalBaseUrl || "http://127.0.0.1:4173"; + +export default defineConfig({ + testDir: "./tests/browser", + fullyParallel: true, + forbidOnly: Boolean(process.env.CI), + retries: process.env.CI ? 2 : 0, + workers: process.env.CI ? 2 : undefined, + timeout: 30_000, + expect: { + timeout: 8_000 + }, + outputDir: ".tmp/test-results", + reporter: [ + ["list"], + ["html", { outputFolder: ".tmp/playwright-report", open: "never" }] + ], + use: { + baseURL, + colorScheme: "light", + screenshot: "only-on-failure", + trace: "retain-on-failure", + video: "retain-on-failure" + }, + webServer: externalBaseUrl + ? undefined + : { + command: "python -m http.server 4173 --bind 127.0.0.1 --directory .tmp/site", + url: baseURL, + reuseExistingServer: !process.env.CI, + timeout: 15_000 + }, + projects: [ + { + name: "chromium", + use: { ...devices["Desktop Chrome"] } + }, + { + name: "firefox", + use: { ...devices["Desktop Firefox"] } + }, + { + name: "webkit", + use: { ...devices["Desktop Safari"] } + }, + { + name: "chromium-dark", + use: { ...devices["Desktop Chrome"], colorScheme: "dark" } + }, + { + name: "mobile-chromium", + use: { ...devices["Pixel 5"] } + }, + { + name: "mobile-webkit", + use: { ...devices["iPhone 12"] } + } + ] +}); diff --git a/requirements-docs.txt b/requirements-docs.txt new file mode 100644 index 0000000..97e43e8 --- /dev/null +++ b/requirements-docs.txt @@ -0,0 +1,6 @@ +mkdocs==1.6.1 +mkdocs-material==9.7.6 +mkdocs-redirects==1.2.2 +PyYAML==6.0.2 +pymdown-extensions==11.0.1 +shapely==2.0.6 diff --git a/schemas/analyzer-location-codes.schema.json b/schemas/analyzer-location-codes.schema.json new file mode 100644 index 0000000..3ab8d20 --- /dev/null +++ b/schemas/analyzer-location-codes.schema.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://meshcore.ca/schemas/analyzer-location-codes.schema.json", + "title": "MeshCore Canada observer location-code quick list", + "type": "object", + "required": ["schema_version", "version", "last_reviewed", "scope", "locations"], + "properties": { + "schema_version": {"const": 1}, + "version": {"type": "string", "pattern": "^[0-9]{4}\\.[0-9]{2}$"}, + "last_reviewed": {"type": "string", "format": "date"}, + "scope": {"type": "string", "minLength": 24}, + "locations": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": ["code", "name", "province", "province_code"], + "properties": { + "code": {"type": "string", "pattern": "^[A-Z]{3}$"}, + "name": {"type": "string", "minLength": 2}, + "province": {"type": "string", "minLength": 4}, + "province_code": {"type": "string", "pattern": "^[A-Z]{2}$"} + }, + "additionalProperties": false + } + } + }, + "additionalProperties": false +} diff --git a/schemas/analyzer-observer-config.schema.json b/schemas/analyzer-observer-config.schema.json new file mode 100644 index 0000000..6a21c49 --- /dev/null +++ b/schemas/analyzer-observer-config.schema.json @@ -0,0 +1,48 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://meshcore.ca/schemas/analyzer-observer-config.schema.json", + "title": "MeshCore Canada observer configuration", + "type": "object", + "required": ["schema_version", "version", "last_reviewed", "network", "brokers", "topics", "verification", "location_codes"], + "properties": { + "schema_version": {"const": 1}, + "version": {"type": "string", "pattern": "^[0-9]{4}\\.[0-9]{2}$"}, + "last_reviewed": {"type": "string", "format": "date"}, + "network": { + "type": "object", + "required": ["preset", "frequency_mhz", "bandwidth_khz", "spreading_factor", "coding_rate", "path_hash_mode", "path_hash_bytes"], + "additionalProperties": false + }, + "brokers": { + "type": "array", + "minItems": 2, + "maxItems": 2, + "items": { + "type": "object", + "required": ["id", "name", "host", "port", "transport", "tls", "verify_tls", "authentication", "token_audience"], + "properties": { + "id": {"enum": ["primary", "backup"]}, + "host": {"type": "string", "pattern": "^mqtt[12]\\.meshcore\\.ca$"}, + "port": {"const": 443}, + "transport": {"const": "websockets"}, + "tls": {"const": true}, + "verify_tls": {"const": true}, + "token_audience": {"type": "string"} + }, + "additionalProperties": true + } + }, + "topics": { + "type": "object", + "required": ["packets", "status"], + "additionalProperties": false + }, + "verification": { + "type": "object", + "required": ["observers", "packets", "map"], + "additionalProperties": false + }, + "location_codes": {"const": "location-codes.json"} + }, + "additionalProperties": false +} diff --git a/schemas/community-directory.schema.json b/schemas/community-directory.schema.json new file mode 100644 index 0000000..ff8e10a --- /dev/null +++ b/schemas/community-directory.schema.json @@ -0,0 +1,377 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://meshcore.ca/schemas/community-directory.schema.json", + "title": "MeshCore Canada community directory", + "type": "object", + "additionalProperties": false, + "required": [ + "schema", + "metadata", + "national_defaults", + "directory_pages", + "province_contacts", + "communities" + ], + "properties": { + "$schema": { + "type": "string" + }, + "schema": { + "const": "meshcore-canada-communities/v1" + }, + "metadata": { + "type": "object", + "additionalProperties": false, + "required": [ + "owner", + "migrated_at", + "source_revision", + "review_by", + "update_route" + ], + "properties": { + "owner": { + "type": "string", + "minLength": 1 + }, + "migrated_at": { + "type": "string", + "format": "date" + }, + "source_revision": { + "type": "string", + "minLength": 7 + }, + "review_by": { + "type": "string", + "format": "date" + }, + "update_route": { + "type": "string", + "minLength": 1 + } + } + }, + "national_defaults": { + "type": "object" + }, + "directory_pages": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/directoryPage" + } + }, + "province_contacts": { + "type": "array", + "items": { + "$ref": "#/$defs/contactWithIdentity" + } + }, + "communities": { + "type": "array", + "items": { + "$ref": "#/$defs/community" + } + } + }, + "$defs": { + "nullableDate": { + "oneOf": [ + { + "type": "string", + "format": "date" + }, + { + "type": "null" + } + ] + }, + "directoryPage": { + "type": "object", + "additionalProperties": false, + "required": [ + "slug", + "title", + "codes", + "aliases" + ], + "properties": { + "slug": { + "type": "string", + "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" + }, + "title": { + "type": "string", + "minLength": 1 + }, + "codes": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "enum": [ + "AB", + "BC", + "MB", + "NB", + "NL", + "NS", + "NT", + "NU", + "ON", + "PE", + "QC", + "SK", + "YT" + ] + } + }, + "aliases": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "minLength": 1 + } + } + } + }, + "contact": { + "type": "object", + "additionalProperties": {}, + "required": [ + "type", + "label", + "url", + "health", + "last_checked" + ], + "properties": { + "type": { + "enum": [ + "discord", + "facebook", + "meshmapper", + "telegram", + "website" + ] + }, + "label": { + "type": "string", + "minLength": 1 + }, + "url": { + "oneOf": [ + { + "type": "string", + "format": "uri", + "pattern": "^https?://" + }, + { + "type": "null" + } + ] + }, + "health": { + "enum": [ + "verified", + "needs-review", + "expired" + ] + }, + "last_checked": { + "$ref": "#/$defs/nullableDate" + } + } + }, + "contactWithIdentity": { + "allOf": [ + { + "$ref": "#/$defs/contact" + }, + { + "type": "object", + "required": [ + "id", + "province" + ], + "properties": { + "id": { + "type": "string" + }, + "province": { + "type": "string" + } + } + } + ] + }, + "community": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "name", + "province", + "service_area", + "places", + "aliases", + "status", + "languages", + "location", + "settings", + "contacts", + "owner", + "verified_at", + "verify_by", + "canonical_route" + ], + "properties": { + "id": { + "type": "string", + "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" + }, + "name": { + "type": "string", + "minLength": 1 + }, + "province": { + "enum": [ + "AB", + "BC", + "MB", + "NB", + "NL", + "NS", + "NT", + "NU", + "ON", + "PE", + "QC", + "SK", + "YT" + ] + }, + "service_area": { + "type": "string", + "minLength": 1 + }, + "places": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "minLength": 1 + } + }, + "aliases": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "minLength": 1 + } + }, + "status": { + "enum": [ + "active", + "forming", + "testing", + "needs-update" + ] + }, + "languages": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "pattern": "^[a-z]{2}(?:-[A-Z]{2})?$" + } + }, + "location": { + "type": "object", + "additionalProperties": false, + "required": [ + "latitude", + "longitude", + "precision" + ], + "properties": { + "latitude": { + "type": [ + "number", + "null" + ], + "minimum": -90, + "maximum": 90 + }, + "longitude": { + "type": [ + "number", + "null" + ], + "minimum": -180, + "maximum": 180 + }, + "precision": { + "enum": [ + "exact", + "approximate", + "service-area" + ] + } + } + }, + "settings": { + "type": "object", + "additionalProperties": false, + "required": [ + "inherit_national", + "overrides" + ], + "properties": { + "inherit_national": { + "type": "boolean" + }, + "overrides": { + "type": "object", + "additionalProperties": false, + "properties": { + "radio_preset": { + "type": "string" + }, + "path_hash_mode": { + "type": "string" + } + } + } + } + }, + "contacts": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/contact" + } + }, + "owner": { + "type": [ + "string", + "null" + ] + }, + "verified_at": { + "$ref": "#/$defs/nullableDate" + }, + "verify_by": { + "$ref": "#/$defs/nullableDate" + }, + "canonical_route": { + "type": "string", + "pattern": "^/provinces/[a-z0-9-]+/#community-[a-z0-9-]+$" + } + } + } + } +} diff --git a/schemas/page-metadata.schema.json b/schemas/page-metadata.schema.json new file mode 100644 index 0000000..09b14ba --- /dev/null +++ b/schemas/page-metadata.schema.json @@ -0,0 +1,91 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://meshcore.ca/schemas/page-metadata.schema.json", + "title": "MeshCore Canada page metadata", + "type": "object", + "required": [ + "title", + "description", + "audience", + "task", + "scope", + "status", + "owner", + "last_reviewed", + "review_by" + ], + "properties": { + "title": { "type": "string", "minLength": 4 }, + "description": { "type": "string", "minLength": 24, "maxLength": 180 }, + "audience": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { "type": "string", "pattern": "^[a-z0-9-]+$" } + }, + "task": { "type": "string", "pattern": "^[a-z0-9-]+$" }, + "scope": { + "type": "string", + "anyOf": [ + { "enum": [ + "canada-baseline", + "upstream-meshcore", + "ottawa-field-practice", + "experimental", + "legacy", + "regulatory-reference" + ] }, + { "pattern": "^province:[a-z]{2}$" }, + { "pattern": "^community:[a-z0-9-]+$" } + ] + }, + "status": { + "enum": ["verified", "draft", "experimental", "legacy", "archived"] + }, + "owner": { "type": "string", "pattern": "^[a-z0-9-]+$" }, + "last_reviewed": { "type": "string", "format": "date" }, + "review_by": { "type": "string", "format": "date" }, + "difficulty": { "enum": ["beginner", "intermediate", "advanced"] }, + "estimated_time": { "type": "string", "minLength": 3 }, + "destructive": { "type": "boolean" }, + "tested_with": { "type": "object", "minProperties": 1 }, + "evidence": { + "oneOf": [ + { "type": "string", "minLength": 3 }, + { "type": "array", "minItems": 1, "items": { "type": "string" } } + ] + }, + "requires": { + "type": "array", + "uniqueItems": true, + "items": { "type": "string", "pattern": "^[a-z0-9-]+$" } + }, + "aliases": { + "type": "array", + "uniqueItems": true, + "items": { "type": "string" } + }, + "page_styles": { + "type": "array", + "uniqueItems": true, + "items": { "type": "string", "pattern": "^[^/].*\\.css$" } + }, + "page_scripts": { + "type": "array", + "uniqueItems": true, + "items": { "type": "string", "pattern": "^[^/].*\\.js$" } + }, + "page_modules": { + "type": "array", + "uniqueItems": true, + "items": { "type": "string", "pattern": "^[^/].*\\.js$" } + } + }, + "allOf": [ + { + "if": { "properties": { "status": { "const": "verified" } } }, + "then": { "anyOf": [{ "required": ["tested_with"] }, { "required": ["evidence"] }] } + } + ], + "additionalProperties": true +} diff --git a/scripts/check-built-links.mjs b/scripts/check-built-links.mjs new file mode 100644 index 0000000..12d4e7e --- /dev/null +++ b/scripts/check-built-links.mjs @@ -0,0 +1,138 @@ +import { readdir, readFile, stat } from "node:fs/promises"; +import { extname, join, relative, resolve, sep } from "node:path"; +import process from "node:process"; + +const siteRoot = resolve(process.argv[2] || ".tmp/site"); +const publicOrigin = "https://meshcore.ca"; +const attributePattern = /\b(?:href|src)=["']([^"']+)["']/gi; +const ignoredSchemes = /^(?:data|mailto|tel|javascript|blob):/i; + +async function walk(directory) { + const files = []; + for (const entry of await readdir(directory, { withFileTypes: true })) { + const path = join(directory, entry.name); + if (entry.isDirectory()) files.push(...await walk(path)); + else files.push(path); + } + return files; +} + +function pagePath(file) { + const local = relative(siteRoot, file).split(sep).join("/"); + if (local === "index.html") return "/"; + if (local.endsWith("/index.html")) return `/${local.slice(0, -"index.html".length)}`; + return `/${local}`; +} + +function decodeAttribute(value) { + return value + .replace(/&/g, "&") + .replace(/"/g, '"') + .replace(/'/g, "'"); +} + +async function isFile(path) { + try { + return (await stat(path)).isFile(); + } catch (_error) { + return false; + } +} + +async function resolveTarget(pathname) { + let decoded; + try { + decoded = decodeURIComponent(pathname); + } catch (_error) { + return { error: "invalid URL encoding" }; + } + const local = decoded.replace(/^\/+/, ""); + const candidates = []; + if (!local) candidates.push(join(siteRoot, "index.html")); + else if (decoded.endsWith("/")) candidates.push(join(siteRoot, local, "index.html")); + else { + candidates.push(join(siteRoot, local)); + if (!extname(local)) { + candidates.push(join(siteRoot, local, "index.html")); + candidates.push(join(siteRoot, `${local}.html`)); + } + } + for (const candidate of candidates) { + if (await isFile(candidate)) return { path: candidate }; + } + return { error: `missing target ${decoded}` }; +} + +async function hasExactCase(path) { + const local = relative(siteRoot, path); + if (!local || local.startsWith("..")) return !local; + let current = siteRoot; + for (const part of local.split(sep)) { + const names = await readdir(current); + if (!names.includes(part)) return false; + current = join(current, part); + } + return true; +} + +function hasAnchor(text, fragment) { + let id; + try { + id = decodeURIComponent(fragment); + } catch (_error) { + return false; + } + const escaped = id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return new RegExp(`\\b(?:id|name)=["']${escaped}["']`).test(text); +} + +async function main() { + const files = await walk(siteRoot); + const htmlFiles = files.filter((file) => file.endsWith(".html")); + const failures = new Set(); + let checked = 0; + + for (const file of htmlFiles) { + const html = await readFile(file, "utf8"); + const base = new URL(pagePath(file), publicOrigin); + for (const match of html.matchAll(attributePattern)) { + const raw = decodeAttribute(match[1].trim()); + if (!raw || raw === "#" || raw.startsWith("//") || ignoredSchemes.test(raw)) continue; + + let url; + try { + url = new URL(raw, base); + } catch (_error) { + failures.add(`${pagePath(file)} -> ${raw}: invalid URL`); + continue; + } + if (url.origin !== publicOrigin) continue; + checked += 1; + const target = await resolveTarget(url.pathname); + if (!target.path) { + failures.add(`${pagePath(file)} -> ${raw}: ${target.error}`); + continue; + } + if (!(await hasExactCase(target.path))) { + failures.add(`${pagePath(file)} -> ${raw}: path casing differs from disk`); + continue; + } + if (url.hash && [".html", ".svg"].includes(extname(target.path).toLowerCase())) { + const targetText = target.path === file ? html : await readFile(target.path, "utf8"); + if (!hasAnchor(targetText, url.hash.slice(1))) { + failures.add(`${pagePath(file)} -> ${raw}: missing fragment target`); + } + } + } + } + + if (failures.size) { + console.error(`Built-link validation failed with ${failures.size} problem(s):`); + for (const failure of [...failures].sort()) console.error(`- ${failure}`); + process.exitCode = 1; + return; + } + console.log(`Built-link validation passed for ${htmlFiles.length} pages and ${checked} local references.`); +} + +await main(); diff --git a/scripts/postprocess-site.mjs b/scripts/postprocess-site.mjs new file mode 100644 index 0000000..36f8fc9 --- /dev/null +++ b/scripts/postprocess-site.mjs @@ -0,0 +1,84 @@ +import { createHash } from "node:crypto"; +import { readdir, readFile, stat, writeFile } from "node:fs/promises"; +import { dirname, join, relative, resolve, sep } from "node:path"; +import { pathToFileURL } from "node:url"; +import process from "node:process"; + +const noIndexPattern = + /]*>/i; +const canonicalPattern = + /]*>/i; + +async function walk(directory) { + const files = []; + for (const entry of await readdir(directory, { withFileTypes: true })) { + const path = join(directory, entry.name); + if (entry.isDirectory()) files.push(...await walk(path)); + else files.push(path); + } + return files; +} + +function fallbackUrl(siteRoot, file, origin) { + const local = relative(siteRoot, file).split(sep).join("/"); + const pathname = local === "index.html" + ? "/" + : `/${local.replace(/(?:^|\/)index\.html$/, "/")}`; + return new URL(pathname, origin).href; +} + +export async function postprocessSite(siteDirectory, options = {}) { + const siteRoot = resolve(siteDirectory); + const sitemapPath = join(siteRoot, "sitemap.xml"); + if (!(await stat(sitemapPath)).isFile()) { + throw new Error(`Missing sitemap: ${sitemapPath}`); + } + + const htmlFiles = (await walk(siteRoot)).filter((file) => file.endsWith(".html")); + const sitemapSource = await readFile(sitemapPath, "utf8"); + const firstLocation = sitemapSource.match(/([^<]+)<\/loc>/)?.[1]; + const origin = firstLocation ? new URL(firstLocation).origin : "https://meshcore.ca"; + const noIndexUrls = new Set(); + + for (const file of htmlFiles) { + const html = await readFile(file, "utf8"); + if (!noIndexPattern.test(html)) continue; + const canonical = html.match(canonicalPattern)?.[1]; + noIndexUrls.add(canonical || fallbackUrl(siteRoot, file, origin)); + } + + const filteredSitemap = sitemapSource.replace( + /\s*\s*([^<]+)<\/loc>[\s\S]*?<\/url>/g, + (block, location) => (noIndexUrls.has(location) ? "" : block), + ); + await writeFile(sitemapPath, `${filteredSitemap.trim()}\n`, "utf8"); + + const manifest = { + schema: "meshcore-canada-site-manifest-v1", + revision: + options.revision || + process.env.MCC_SITE_REVISION || + process.env.GITHUB_SHA || + "local-preview", + generatedAt: options.generatedAt || new Date().toISOString(), + siteOrigin: origin, + pageCount: htmlFiles.length, + indexedPageCount: htmlFiles.length - noIndexUrls.size, + noIndexPageCount: noIndexUrls.size, + sitemapSha256: createHash("sha256").update(filteredSitemap).digest("hex"), + }; + await writeFile( + join(siteRoot, "site-manifest.json"), + `${JSON.stringify(manifest, null, 2)}\n`, + "utf8", + ); + return manifest; +} + +if (process.argv[1] && pathToFileURL(resolve(process.argv[1])).href === import.meta.url) { + const siteRoot = process.argv[2] || ".tmp/site"; + const manifest = await postprocessSite(siteRoot); + console.log( + `Post-processed ${manifest.pageCount} pages; ${manifest.noIndexPageCount} excluded from indexing.`, + ); +} diff --git a/scripts/run-lighthouse.mjs b/scripts/run-lighthouse.mjs new file mode 100644 index 0000000..c388106 --- /dev/null +++ b/scripts/run-lighthouse.mjs @@ -0,0 +1,106 @@ +import { spawn } from "node:child_process"; +import { mkdir, writeFile } from "node:fs/promises"; +import process from "node:process"; +import { launch } from "chrome-launcher"; +import lighthouse, { desktopConfig } from "lighthouse"; + +const suppliedBaseUrl = process.env.LIGHTHOUSE_BASE_URL; +const baseUrl = suppliedBaseUrl || "http://127.0.0.1:4174"; +const routes = [ + ["home", "/"], + ["start", "/start/"], + ["communities", "/provinces/"], + ["config", "/config/"], + ["submit-idea", "/submit-idea/"] +]; +const budgets = { + performance: 0.8, + accessibility: 0.95, + "best-practices": 0.9, + seo: 0.9 +}; + +let server; +let chrome; + +function startServer() { + if (suppliedBaseUrl) return undefined; + const command = process.platform === "win32" ? "python.exe" : "python3"; + return spawn( + command, + ["-m", "http.server", "4174", "--bind", "127.0.0.1", "--directory", ".tmp/site"], + { stdio: "ignore", windowsHide: true } + ); +} + +async function waitForServer(url, attempts = 50) { + for (let attempt = 0; attempt < attempts; attempt += 1) { + try { + const response = await fetch(url); + if (response.ok) return; + } catch (_error) { + // The bounded retry below handles server startup. + } + await new Promise((resolve) => setTimeout(resolve, 200)); + } + throw new Error(`Timed out waiting for ${url}`); +} + +function scoreSummary(result) { + return Object.fromEntries( + Object.keys(budgets).map((name) => [name, result.lhr.categories[name]?.score ?? 0]) + ); +} + +function hasNoIndex(html) { + return /]*content=["'][^"']*\bnoindex\b/i.test(html); +} + +async function run() { + server = startServer(); + await waitForServer(baseUrl); + await mkdir(".tmp/lighthouse", { recursive: true }); + chrome = await launch({ + chromePath: process.env.CHROME_PATH || undefined, + chromeFlags: ["--headless=new", "--no-sandbox", "--disable-dev-shm-usage"] + }); + + const failures = []; + for (const [name, route] of routes) { + const url = new URL(route, baseUrl).href; + const html = await (await fetch(url)).text(); + const intentionallyNoIndex = hasNoIndex(html); + const result = await lighthouse(url, { + port: chrome.port, + output: "json", + logLevel: "error", + onlyCategories: Object.keys(budgets) + }, desktopConfig); + if (!result) throw new Error(`Lighthouse returned no result for ${url}`); + + await writeFile( + `.tmp/lighthouse/${name}.json`, + typeof result.report === "string" ? result.report : JSON.stringify(result.report), + "utf8" + ); + const scores = scoreSummary(result); + console.log(`${name}: ${JSON.stringify(scores)}`); + for (const [category, minimum] of Object.entries(budgets)) { + if (category === "seo" && intentionallyNoIndex) continue; + if (scores[category] < minimum) { + failures.push(`${name} ${category} ${scores[category]} < ${minimum}`); + } + } + } + + if (failures.length) { + throw new Error(`Lighthouse budgets failed:\n- ${failures.join("\n- ")}`); + } +} + +try { + await run(); +} finally { + if (chrome) await chrome.kill(); + if (server) server.kill(); +} diff --git a/scripts/validate-communities.py b/scripts/validate-communities.py new file mode 100644 index 0000000..50b1f98 --- /dev/null +++ b/scripts/validate-communities.py @@ -0,0 +1,848 @@ +#!/usr/bin/env python3 +"""Validate community data and keep the public directory pages generated from it.""" + +from __future__ import annotations + +import argparse +import html +import json +import re +import sys +import unicodedata +from collections import Counter +from datetime import date +from pathlib import Path +from typing import Any +from urllib.parse import urlparse + + +ROOT = Path(__file__).resolve().parents[1] +DATA_PATH = ROOT / "data" / "communities.json" +SCHEMA_PATH = ROOT / "schemas" / "community-directory.schema.json" +PROVINCES_DIR = ROOT / "docs" / "provinces" + +SCHEMA_VERSION = "meshcore-canada-communities/v1" +VALID_CODES = { + "AB", + "BC", + "MB", + "NB", + "NL", + "NS", + "NT", + "NU", + "ON", + "PE", + "QC", + "SK", + "YT", +} +VALID_STATUSES = {"active", "forming", "testing", "needs-update"} +VALID_CONTACT_TYPES = {"discord", "facebook", "meshmapper", "telegram", "website"} +VALID_CONTACT_HEALTH = {"verified", "needs-review", "expired"} +ID_PATTERN = re.compile(r"^[a-z0-9]+(?:-[a-z0-9]+)*$") + + +class Validation: + def __init__(self) -> None: + self.errors: list[str] = [] + self.warnings: list[str] = [] + + def error(self, message: str) -> None: + self.errors.append(message) + + def warn(self, message: str) -> None: + self.warnings.append(message) + + +def load_data() -> dict[str, Any]: + return json.loads(DATA_PATH.read_text(encoding="utf-8")) + + +def parse_date(value: Any, field: str, check: Validation, *, nullable: bool = False) -> date | None: + if value is None and nullable: + return None + if not isinstance(value, str): + check.error(f"{field} must be an ISO date") + return None + try: + return date.fromisoformat(value) + except ValueError: + check.error(f"{field} must be an ISO date, got {value!r}") + return None + + +def normalized(value: str) -> str: + folded = unicodedata.normalize("NFKD", value) + return " ".join("".join(char for char in folded if not unicodedata.combining(char)).casefold().split()) + + +def route_for_page(page: dict[str, Any]) -> str: + return f"/provinces/{page['slug']}/" + + +def page_by_code(data: dict[str, Any]) -> dict[str, dict[str, Any]]: + return { + code: page + for page in data["directory_pages"] + for code in page["codes"] + } + + +def validate_contact(contact: Any, label: str, check: Validation, *, with_identity: bool = False) -> None: + if not isinstance(contact, dict): + check.error(f"{label} must be an object") + return + required = {"type", "label", "url", "health", "last_checked"} + if with_identity: + required |= {"id", "province"} + missing = sorted(required - contact.keys()) + unexpected = sorted(contact.keys() - required) + if unexpected: + check.error(f"{label} has unsupported fields: {', '.join(unexpected)}") + if missing: + check.error(f"{label} is missing: {', '.join(missing)}") + return + if contact["type"] not in VALID_CONTACT_TYPES: + check.error(f"{label}.type is not allowed: {contact['type']!r}") + if not isinstance(contact["label"], str) or not contact["label"].strip(): + check.error(f"{label}.label must be non-empty") + url = contact["url"] + if url is not None: + parsed = urlparse(url) + if parsed.scheme not in {"http", "https"} or not parsed.netloc: + check.error(f"{label}.url must be an absolute HTTP(S) URL") + elif contact["health"] != "needs-review": + check.error(f"{label} without a URL must have needs-review health") + if contact["health"] not in VALID_CONTACT_HEALTH: + check.error(f"{label}.health is not allowed: {contact['health']!r}") + checked = parse_date(contact["last_checked"], f"{label}.last_checked", check, nullable=True) + if contact["health"] == "verified" and checked is None: + check.error(f"{label} marked verified must have last_checked") + if contact["health"] == "expired" and url is None: + check.error(f"{label} marked expired must retain the expired URL for review") + if with_identity: + if not isinstance(contact["id"], str) or not ID_PATTERN.fullmatch(contact["id"]): + check.error(f"{label}.id must be a stable kebab-case ID") + if contact["province"] not in VALID_CODES: + check.error(f"{label}.province is not a Canadian province/territory code") + + +def validate_data(data: dict[str, Any]) -> Validation: + check = Validation() + if data.get("schema") != SCHEMA_VERSION: + check.error(f"schema must be {SCHEMA_VERSION!r}") + if not SCHEMA_PATH.is_file(): + check.error(f"schema file is missing: {SCHEMA_PATH.relative_to(ROOT)}") + + metadata = data.get("metadata") + if not isinstance(metadata, dict): + check.error("metadata must be an object") + metadata = {} + for field in ("owner", "source_revision", "update_route"): + if not isinstance(metadata.get(field), str) or not metadata[field].strip(): + check.error(f"metadata.{field} must be non-empty") + parse_date(metadata.get("migrated_at"), "metadata.migrated_at", check) + parse_date(metadata.get("review_by"), "metadata.review_by", check) + + defaults = data.get("national_defaults") + if not isinstance(defaults, dict): + check.error("national_defaults must be an object") + defaults = {} + for field in ("radio_preset", "raw_radio", "path_hash_mode", "cli_path_setting"): + if field not in defaults: + check.error(f"national_defaults.{field} is required") + + pages = data.get("directory_pages") + if not isinstance(pages, list) or not pages: + check.error("directory_pages must be a non-empty array") + pages = [] + slugs: set[str] = set() + all_codes: list[str] = [] + for index, page in enumerate(pages): + label = f"directory_pages[{index}]" + if not isinstance(page, dict): + check.error(f"{label} must be an object") + continue + slug = page.get("slug") + if not isinstance(slug, str) or not ID_PATTERN.fullmatch(slug): + check.error(f"{label}.slug must be kebab-case") + elif slug in slugs: + check.error(f"duplicate directory page slug: {slug}") + else: + slugs.add(slug) + if not isinstance(page.get("title"), str) or not page["title"].strip(): + check.error(f"{label}.title must be non-empty") + codes = page.get("codes") + if not isinstance(codes, list) or not codes: + check.error(f"{label}.codes must be a non-empty array") + codes = [] + for code in codes: + if code not in VALID_CODES: + check.error(f"{label} has invalid code {code!r}") + all_codes.append(code) + aliases = page.get("aliases") + if not isinstance(aliases, list) or any(not isinstance(alias, str) or not alias.strip() for alias in aliases): + check.error(f"{label}.aliases must contain non-empty strings") + + duplicated_codes = sorted(code for code, count in Counter(all_codes).items() if count > 1) + missing_codes = sorted(VALID_CODES - set(all_codes)) + if duplicated_codes: + check.error(f"jurisdiction codes appear on multiple pages: {', '.join(duplicated_codes)}") + if missing_codes: + check.error(f"jurisdiction codes have no directory page: {', '.join(missing_codes)}") + + code_pages = page_by_code(data) if pages else {} + province_contacts = data.get("province_contacts") + if not isinstance(province_contacts, list): + check.error("province_contacts must be an array") + province_contacts = [] + province_contact_ids: set[str] = set() + for index, contact in enumerate(province_contacts): + label = f"province_contacts[{index}]" + validate_contact(contact, label, check, with_identity=True) + if isinstance(contact, dict) and contact.get("id") in province_contact_ids: + check.error(f"duplicate province contact ID: {contact['id']}") + elif isinstance(contact, dict): + province_contact_ids.add(contact.get("id")) + + communities = data.get("communities") + if not isinstance(communities, list): + check.error("communities must be an array") + communities = [] + ids: set[str] = set() + exact_communities: set[tuple[str, str, str]] = set() + for index, community in enumerate(communities): + label = f"communities[{index}]" + if not isinstance(community, dict): + check.error(f"{label} must be an object") + continue + community_id = community.get("id") + if not isinstance(community_id, str) or not ID_PATTERN.fullmatch(community_id): + check.error(f"{label}.id must be a stable kebab-case ID") + community_id = f"invalid-{index}" + elif community_id in ids: + check.error(f"duplicate community ID: {community_id}") + else: + ids.add(community_id) + + for field in ("name", "service_area"): + if not isinstance(community.get(field), str) or not community[field].strip(): + check.error(f"{label}.{field} must be non-empty") + province = community.get("province") + if province not in VALID_CODES: + check.error(f"{label}.province is invalid: {province!r}") + elif province not in code_pages: + check.error(f"{label}.province has no directory page: {province}") + status = community.get("status") + if status not in VALID_STATUSES: + check.error(f"{label}.status is invalid: {status!r}") + + for field in ("places", "aliases", "languages"): + values = community.get(field) + if not isinstance(values, list) or any(not isinstance(value, str) or not value.strip() for value in values): + check.error(f"{label}.{field} must be an array of non-empty strings") + elif len({normalized(value) for value in values}) != len(values): + check.error(f"{label}.{field} contains duplicate normalized values") + + location = community.get("location") + if not isinstance(location, dict): + check.error(f"{label}.location must be an object") + else: + latitude = location.get("latitude") + longitude = location.get("longitude") + if (latitude is None) != (longitude is None): + check.error(f"{label}.location must provide both latitude and longitude or neither") + if latitude is not None and not (-90 <= latitude <= 90): + check.error(f"{label}.location.latitude is out of range") + if longitude is not None and not (-180 <= longitude <= 180): + check.error(f"{label}.location.longitude is out of range") + if location.get("precision") not in {"exact", "approximate", "service-area"}: + check.error(f"{label}.location.precision is invalid") + + settings = community.get("settings") + if not isinstance(settings, dict) or not isinstance(settings.get("inherit_national"), bool): + check.error(f"{label}.settings must declare inherit_national") + settings = {"overrides": {}} + overrides = settings.get("overrides") + if not isinstance(overrides, dict): + check.error(f"{label}.settings.overrides must be an object") + overrides = {} + for field, value in overrides.items(): + if field not in {"radio_preset", "path_hash_mode"}: + check.error(f"{label}.settings.overrides has unsupported field {field!r}") + if value == defaults.get(field): + check.error(f"{label} repeats inherited national {field} as an override") + + contacts = community.get("contacts") + if not isinstance(contacts, list) or not contacts: + check.error(f"{label}.contacts must be a non-empty array") + contacts = [] + contact_keys: set[tuple[Any, Any, Any]] = set() + for contact_index, contact in enumerate(contacts): + contact_label = f"{label}.contacts[{contact_index}]" + validate_contact(contact, contact_label, check) + if isinstance(contact, dict): + key = (contact.get("type"), contact.get("label"), contact.get("url")) + if key in contact_keys: + check.error(f"{label} contains an exact duplicate contact") + contact_keys.add(key) + + if "owner" not in community or ( + community["owner"] is not None + and (not isinstance(community["owner"], str) or not community["owner"].strip()) + ): + check.error(f"{label}.owner must be a non-empty string or null") + verified = parse_date(community.get("verified_at"), f"{label}.verified_at", check, nullable=True) + verify_by = parse_date(community.get("verify_by"), f"{label}.verify_by", check, nullable=True) + if (verified is None) != (verify_by is None): + check.error(f"{label} must set verified_at and verify_by together") + if verified is not None and verify_by is not None and verify_by < verified: + check.error(f"{label}.verify_by cannot precede verified_at") + if verified is None: + check.warn(f"{community.get('name', community_id)} needs a community verification date") + + expected_route = "" + if province in code_pages: + expected_route = f"{route_for_page(code_pages[province])}#community-{community_id}" + if community.get("canonical_route") != expected_route: + check.error( + f"{label}.canonical_route must be {expected_route!r}, " + f"got {community.get('canonical_route')!r}" + ) + + if all(isinstance(community.get(field), str) for field in ("name", "service_area")): + exact_key = (province, normalized(community["name"]), normalized(community["service_area"])) + if exact_key in exact_communities: + check.error(f"exact duplicate community: {community['name']} ({province})") + exact_communities.add(exact_key) + + stoonmesh = next((item for item in communities if item.get("id") == "stoonmesh"), None) + if not stoonmesh: + check.error("StoonMesh listing is missing") + elif stoonmesh.get("settings", {}).get("overrides", {}).get("path_hash_mode") != "1-byte": + check.error("StoonMesh must retain its reviewed 1-byte path-hash override") + if defaults.get("path_hash_mode") != "3-byte": + check.error("the national path-hash baseline must remain 3-byte") + + return check + + +def front_matter(*, title: str, description: str, task: str, metadata: dict[str, Any], scripts: bool) -> str: + lines = [ + "---", + f"title: {title}", + f"description: {description}", + "audience:", + " - community-seeker", + " - community-maintainer", + f"task: {task}", + "scope: canada-baseline", + "status: draft", + f"owner: {metadata['owner']}", + f"last_reviewed: {metadata['migrated_at']}", + f"review_by: {metadata['review_by']}", + "difficulty: beginner", + "estimated_time: 2-5 minutes", + "destructive: false", + "page_styles:", + " - assets/styles/communities.css", + ] + if scripts: + lines.extend( + [ + "page_scripts:", + " - assets/javascripts/communities.js", + ] + ) + lines.extend(["---", ""]) + return "\n".join(lines) + + +def status_label(status: str) -> str: + return { + "active": "Active", + "forming": "Forming", + "testing": "Testing", + "needs-update": "Needs update", + }[status] + + +def verification_label(community: dict[str, Any]) -> str: + if community["verified_at"] is None: + return "Not yet verified" + return community["verified_at"] + + +def search_text(community: dict[str, Any], page: dict[str, Any]) -> str: + values = [ + community["name"], + community["service_area"], + community["province"], + page["title"], + *page["aliases"], + *community["places"], + *community["aliases"], + ] + return " ".join(dict.fromkeys(normalized(value) for value in values)) + + +def render_contacts(community: dict[str, Any], *, indent: str = "") -> list[str]: + lines: list[str] = [] + for contact in community["contacts"]: + if contact["url"]: + value = ( + f'' + f'{html.escape(contact["label"])} ' + '(external)' + ) + else: + value = html.escape(contact["label"]) + lines.append(f"{indent}
  • {contact['type'].title()}: {value}
  • ") + return lines + + +def render_settings(community: dict[str, Any], *, compact: bool = False) -> str: + overrides = community["settings"]["overrides"] + if not overrides: + return "Uses the Canada baseline" + values = [] + if "radio_preset" in overrides: + values.append( + f"Radio preset: {html.escape(overrides['radio_preset'])}" + ) + if "path_hash_mode" in overrides: + values.append( + f"Path hash mode: {html.escape(overrides['path_hash_mode'])}" + ) # Cards use raw HTML, so override values must be escaped markup. + prefix = "Local override — " if compact else "" + return prefix + "; ".join(values) + + +def render_directory_card(community: dict[str, Any], page: dict[str, Any]) -> str: + search = html.escape(search_text(community, page), quote=True) + has_override = str(bool(community["settings"]["overrides"])).lower() + lines = [ + ( + f'", + ] + return "\n".join(lines) + + +def render_index(data: dict[str, Any]) -> str: + metadata = data["metadata"] + pages = data["directory_pages"] + communities = data["communities"] + code_pages = page_by_code(data) + active = sum(item["status"] == "active" for item in communities) + forming = sum(item["status"] == "forming" for item in communities) + overrides = sum(bool(item["settings"]["overrides"]) for item in communities) + + lines = [ + front_matter( + title="Find a MeshCore community in Canada", + description="Search Canadian MeshCore communities by place, province, community name, or common alias.", + task="find-community", + metadata=metadata, + scripts=True, + ).rstrip(), + "", + "", + "", + "# Find a MeshCore community", + "", + "Search by place, province, community name, or a common alias. The full list", + "works without a map, location permission, or a GitHub account.", + "", + '
    ', + f"{len(communities)} listings", + f"{active} active", + f"{forming} forming", + f"{overrides} local override", + "
    ", + "", + '
    ', + ' ", + '
    ', + ' ', + ' ", + "
    ", + ' ", + ' ', + ' ', + f" Showing {len(communities)} communities", + " ", + "
    ", + "", + '", + "", + "## Communities", + "", + '
    ', + ] + for community in communities: + lines.append(render_directory_card(community, code_pages[community["province"]])) + lines.extend( + [ + "
    ", + "", + "## Canada baseline", + "", + "Use these settings only when your local community has not published an override.", + "", + "| Setting | Canada baseline |", + "|---|---|", + f'| Radio preset | `{data["national_defaults"]["radio_preset"]}` |', + ( + "| Raw radio values | " + f'`{data["national_defaults"]["raw_radio"]["frequency_mhz"]} MHz / ' + f'{data["national_defaults"]["raw_radio"]["bandwidth_khz"]} kHz / ' + f'SF{data["national_defaults"]["raw_radio"]["spreading_factor"]} / ' + f'CR{data["national_defaults"]["raw_radio"]["coding_rate"]}` |' + ), + f'| Path hash mode | `{data["national_defaults"]["path_hash_mode"]}` |', + f'| Command-line path setting | `{data["national_defaults"]["cli_path_setting"]}` |', + "", + "!!! warning \"Check local settings first\"", + " Nearby devices need matching settings. A card marked **Local override**", + " takes precedence over the Canada baseline after you confirm it with the", + " listed community.", + "", + "## Browse by province or territory", + "", + '
    ', + ] + ) + for page in pages: + page_communities = [ + item for item in communities if item["province"] in page["codes"] + ] + page_active = sum(item["status"] == "active" for item in page_communities) + page_forming = sum(item["status"] == "forming" for item in page_communities) + labels = [] + if page_active: + labels.append(f"{page_active} active") + if page_forming: + labels.append(f"{page_forming} forming") + if not labels: + labels.append("No listing yet") + lines.extend( + [ + '", + ] + ) + lines.extend( + [ + "
    ", + "", + "## Add or update a listing", + "", + "Found missing or outdated information?", + f"[Send a community update]({metadata['update_route']}). No GitHub account is needed.", + "", + "Directory contacts are community-provided external links. **Not yet verified**", + "means the directory stewards have preserved the listing but have not recorded a", + "recent contact check. Use the update action if a link has expired.", + "", + ] + ) + return "\n".join(lines) + + +def render_community_card(community: dict[str, Any], metadata: dict[str, Any]) -> str: + lines = [ + ( + f'
    ' + ), + '
    ', + f"

    {html.escape(community['name'])}

    ", + ( + f'' + f'{status_label(community["status"])}' + ), + "
    ", + f'

    {html.escape(community["service_area"])}

    ', + "
    ", + "
    Settings
    ", + f"
    {render_settings(community)}
    ", + "
    Last verified
    ", + f"
    {verification_label(community)}
    ", + "
    ", + ] + if community["settings"]["overrides"]: + lines.extend( + [ + '
    ', + "Local settings override", + "

    Confirm this setting with the community before changing a node.

    ", + "
    ", + ] + ) + if community["status"] == "forming": + lines.extend( + [ + '

    ', + "This group is forming. Contact it to learn what is working and where help is needed.", + "

    ", + ] + ) + lines.extend( + [ + "

    Contacts

    ", + '
      ', + *render_contacts(community), + "
    ", + '

    ', + "Contact check: Not yet verified", + "

    ", + ( + '

    ' + "Update this listing

    " + ), + "
    ", + ] + ) + return "\n".join(lines) + + +def render_province_page(data: dict[str, Any], page: dict[str, Any]) -> str: + metadata = data["metadata"] + communities = [ + item for item in data["communities"] if item["province"] in page["codes"] + ] + province_contacts = [ + item for item in data["province_contacts"] if item["province"] in page["codes"] + ] + active = sum(item["status"] == "active" for item in communities) + forming = sum(item["status"] == "forming" for item in communities) + title_display = page["title"] + description = ( + f"Find MeshCore community contacts, service areas, and local settings for {title_display}." + ) + lines = [ + front_matter( + title=f"MeshCore communities in {title_display}", + description=description, + task="browse-community-directory", + metadata=metadata, + scripts=False, + ).rstrip(), + "", + "", + "", + f"# MeshCore communities in {title_display}", + "", + ] + if communities: + summary_parts = [] + if active: + summary_parts.append(f"{active} active") + if forming: + summary_parts.append(f"{forming} forming") + lines.extend( + [ + f"This directory currently has **{len(communities)}** listing" + f"{'' if len(communities) == 1 else 's'}: {', '.join(summary_parts)}.", + "", + "All listings inherit the [Canada baseline](index.md#canada-baseline) unless a", + "card shows a local override.", + "", + ] + ) + else: + lines.extend( + [ + "There is no reviewed community listing here yet.", + "", + '
    ', + "

    Help add the first listing

    ", + "

    Share the community name, service area, status, and a public contact link.

    ", + '

    ' + "Add a community

    ", + '

    Browse all Canadian communities

    ', + "
    ", + "", + "Until a reviewed local listing publishes an override, start with the", + "[Canada baseline](index.md#canada-baseline) and confirm settings with nearby", + "operators before transmitting.", + "", + ] + ) + + override_communities = [ + item for item in communities if item["settings"]["overrides"] + ] + if override_communities: + lines.extend( + [ + "!!! warning \"Local setting differs from the Canada baseline\"", + " One community on this page publishes a local setting override. Confirm", + " the current setting with its contact before configuring or changing a node.", + "", + ] + ) + + if communities: + lines.extend(["## Community listings", "", '
    ']) + for community in communities: + lines.append(render_community_card(community, metadata)) + lines.extend(["
    ", ""]) + + if province_contacts: + lines.extend(["## Province-wide contacts", "", '
    ']) + for contact in province_contacts: + if contact["url"]: + rendered = ( + f'' + f'{html.escape(contact["label"])} ' + '(external)' + ) + else: + rendered = html.escape(contact["label"]) + lines.extend( + [ + f"

    {contact['type'].title()}: {rendered}

    ", + "

    Contact check: Not yet verified

    ", + ] + ) + lines.extend(["
    ", ""]) + + lines.extend( + [ + "## Add or update a listing", + "", + f"[Send a community update]({metadata['update_route']}). No GitHub account is needed.", + "", + "The directory does not guess exact locations, languages, owners, or link health.", + "Fields remain marked unverified until a community steward reviews them.", + "", + ] + ) + return "\n".join(lines) + + +def generated_pages(data: dict[str, Any]) -> dict[Path, str]: + pages = {PROVINCES_DIR / "index.md": render_index(data)} + for page in data["directory_pages"]: + pages[PROVINCES_DIR / f"{page['slug']}.md"] = render_province_page(data, page) + return pages + + +def check_or_write_generated( + data: dict[str, Any], + check: Validation, + *, + write: bool, +) -> None: + for path, expected in generated_pages(data).items(): + expected = expected.rstrip() + "\n" + if write: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(expected, encoding="utf-8", newline="\n") + continue + if not path.is_file(): + check.error(f"generated page is missing: {path.relative_to(ROOT)}") + continue + actual = path.read_text(encoding="utf-8") + if actual != expected: + check.error( + f"{path.relative_to(ROOT)} is out of date; " + "run: python scripts/validate-communities.py --write" + ) + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument( + "--write", + action="store_true", + help="write generated province and directory pages after validating data", + ) + args = parser.parse_args() + + try: + data = load_data() + except (OSError, json.JSONDecodeError) as exc: + print(f"ERROR: cannot read {DATA_PATH.relative_to(ROOT)}: {exc}", file=sys.stderr) + return 1 + + check = validate_data(data) + if not check.errors: + check_or_write_generated(data, check, write=args.write) + + for warning in check.warnings: + print(f"WARNING: {warning}") + for error in check.errors: + print(f"ERROR: {error}", file=sys.stderr) + + if check.errors: + print( + f"Community directory validation failed with {len(check.errors)} error(s).", + file=sys.stderr, + ) + return 1 + + communities = data["communities"] + active = sum(item["status"] == "active" for item in communities) + forming = sum(item["status"] == "forming" for item in communities) + mode = "generated and validated" if args.write else "validated" + print( + f"Community directory {mode}: {len(communities)} listings " + f"({active} active, {forming} forming), " + f"{len(data['directory_pages'])} directory pages." + ) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/validate-content.py b/scripts/validate-content.py new file mode 100644 index 0000000..c2680e4 --- /dev/null +++ b/scripts/validate-content.py @@ -0,0 +1,224 @@ +#!/usr/bin/env python3 +"""Fail-closed validation for public MeshCore Canada documentation pages.""" + +from __future__ import annotations + +import argparse +import datetime as dt +import re +import sys +from collections import defaultdict +from pathlib import Path +from typing import Any + +import yaml + + +REQUIRED = ( + "title", + "description", + "audience", + "task", + "scope", + "status", + "owner", + "last_reviewed", + "review_by", +) +STATUSES = {"verified", "draft", "experimental", "legacy", "archived"} +FIXED_SCOPES = { + "canada-baseline", + "upstream-meshcore", + "ottawa-field-practice", + "experimental", + "legacy", + "regulatory-reference", +} +SLUG = re.compile(r"^[a-z0-9-]+$") +H1 = re.compile(r"^#(?!#)\s+(.+?)\s*$", re.MULTILINE) +HEADING = re.compile(r"^#{2,6}\s+(.+?)\s*$", re.MULTILINE) +ASSET_KEYS = ("page_styles", "page_scripts", "page_modules") + + +def parse_date(value: Any) -> dt.date | None: + if isinstance(value, dt.datetime): + return value.date() + if isinstance(value, dt.date): + return value + if isinstance(value, str): + try: + return dt.date.fromisoformat(value) + except ValueError: + return None + return None + + +def read_page(path: Path) -> tuple[dict[str, Any], str, list[str]]: + errors: list[str] = [] + text = path.read_text(encoding="utf-8-sig") + lines = text.splitlines() + if not lines or lines[0].strip() != "---": + return {}, text, ["missing YAML front matter at the first line"] + + try: + closing = next(index for index, line in enumerate(lines[1:], 1) if line.strip() == "---") + except StopIteration: + return {}, text, ["front matter is not closed with ---"] + + try: + metadata = yaml.safe_load("\n".join(lines[1:closing])) or {} + except yaml.YAMLError as exc: + return {}, "\n".join(lines[closing + 1 :]), [f"invalid YAML front matter: {exc}"] + if not isinstance(metadata, dict): + errors.append("front matter must be a mapping") + metadata = {} + return metadata, "\n".join(lines[closing + 1 :]), errors + + +def validate_page(path: Path, docs_root: Path, today: dt.date) -> tuple[dict[str, Any], list[str]]: + metadata, body, errors = read_page(path) + for key in REQUIRED: + if key not in metadata or metadata[key] in (None, "", []): + errors.append(f"missing required metadata: {key}") + + title = metadata.get("title") + description = metadata.get("description") + audience = metadata.get("audience") + task = metadata.get("task") + scope = metadata.get("scope") + status = metadata.get("status") + owner = metadata.get("owner") + + if isinstance(title, str): + if len(title.strip()) < 4: + errors.append("title must contain at least four characters") + if title.strip().casefold() == "overview": + errors.append("title cannot be the unqualified word Overview") + elif title is not None: + errors.append("title must be a string") + + if isinstance(description, str): + length = len(description.strip()) + if not 24 <= length <= 180: + errors.append("description must be 24-180 characters") + elif description is not None: + errors.append("description must be a string") + + if not isinstance(audience, list) or not audience: + if audience is not None: + errors.append("audience must be a non-empty list") + elif any(not isinstance(value, str) or not SLUG.fullmatch(value) for value in audience): + errors.append("audience values must be lowercase slugs") + + for key, value in (("task", task), ("owner", owner)): + if value is not None and (not isinstance(value, str) or not SLUG.fullmatch(value)): + errors.append(f"{key} must be a lowercase slug") + + if isinstance(scope, str): + valid_scope = ( + scope in FIXED_SCOPES + or re.fullmatch(r"province:[a-z]{2}", scope) + or re.fullmatch(r"community:[a-z0-9-]+", scope) + ) + if not valid_scope: + errors.append(f"unsupported scope: {scope}") + elif scope is not None: + errors.append("scope must be a string") + + if status is not None and status not in STATUSES: + errors.append(f"unsupported status: {status}") + if status == "verified" and not metadata.get("tested_with") and not metadata.get("evidence"): + errors.append("verified pages require tested_with or evidence") + + last_reviewed = parse_date(metadata.get("last_reviewed")) + review_by = parse_date(metadata.get("review_by")) + if metadata.get("last_reviewed") is not None and last_reviewed is None: + errors.append("last_reviewed must be an ISO date") + if metadata.get("review_by") is not None and review_by is None: + errors.append("review_by must be an ISO date") + if last_reviewed and review_by and last_reviewed > review_by: + errors.append("review_by cannot be earlier than last_reviewed") + if status == "verified" and review_by and review_by < today: + errors.append(f"verified page review expired on {review_by.isoformat()}") + + headings = H1.findall(body) + if len(headings) != 1: + errors.append(f"expected exactly one H1, found {len(headings)}") + if len(re.sub(r"[\s#*_`<>-]", "", body)) < 80: + errors.append("page body is empty or materially incomplete") + + if metadata.get("destructive") is True: + section_names = [value.casefold() for value in HEADING.findall(body)] + required_sections = { + "preflight or backup": ("before", "preflight", "backup"), + "verification": ("verify", "verification"), + "recovery": ("recovery", "undo", "restore"), + } + for label, words in required_sections.items(): + if not any(any(word in section for word in words) for section in section_names): + errors.append(f"destructive page is missing a {label} heading") + + for key in ASSET_KEYS: + values = metadata.get(key, []) + if values is None: + continue + if not isinstance(values, list) or any(not isinstance(value, str) for value in values): + errors.append(f"{key} must be a list of relative asset paths") + continue + for value in values: + if value.startswith(("/", "http://", "https://")) or ".." in Path(value).parts: + errors.append(f"{key} contains a non-local path: {value}") + continue + if not (docs_root / value).is_file(): + errors.append(f"{key} asset does not exist: {value}") + + return metadata, errors + + +def validate_tree(docs_root: Path, today: dt.date) -> list[str]: + problems: list[str] = [] + titles: dict[str, list[Path]] = defaultdict(list) + descriptions: dict[str, list[Path]] = defaultdict(list) + pages = sorted(path for path in docs_root.rglob("*.md") if "assets" not in path.relative_to(docs_root).parts) + + for path in pages: + metadata, errors = validate_page(path, docs_root, today) + relative = path.relative_to(docs_root).as_posix() + problems.extend(f"{relative}: {error}" for error in errors) + if isinstance(metadata.get("title"), str): + titles[metadata["title"].strip().casefold()].append(path) + if isinstance(metadata.get("description"), str): + descriptions[metadata["description"].strip().casefold()].append(path) + + for label, values in (("title", titles), ("description", descriptions)): + for duplicate, paths in values.items(): + if duplicate and len(paths) > 1: + joined = ", ".join(path.relative_to(docs_root).as_posix() for path in paths) + problems.append(f"duplicate {label}: {joined}") + + if not pages: + problems.append("no Markdown pages found") + return problems + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--docs", type=Path, default=Path("docs")) + parser.add_argument("--today", type=dt.date.fromisoformat, default=dt.date.today()) + args = parser.parse_args() + + docs_root = args.docs.resolve() + problems = validate_tree(docs_root, args.today) + if problems: + print(f"Content validation failed with {len(problems)} problem(s):", file=sys.stderr) + for problem in problems: + print(f"- {problem}", file=sys.stderr) + return 1 + + page_count = sum(1 for path in docs_root.rglob("*.md") if "assets" not in path.relative_to(docs_root).parts) + print(f"Content validation passed for {page_count} pages.") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/validate-regions.js b/scripts/validate-regions.cjs similarity index 99% rename from scripts/validate-regions.js rename to scripts/validate-regions.cjs index 0e9a1e0..efd6c55 100644 --- a/scripts/validate-regions.js +++ b/scripts/validate-regions.cjs @@ -814,7 +814,7 @@ function validate(data, meshMapper, partition, digitalPartition, qa, municipalOv "command generation must fall back to bounded region put lines" ); check(scriptText.includes("Provinces and territories may be mixed."), "wide-coverage UI does not support cross-province selection"); - check(scriptText.includes("Large or cross-border coverage"), "UI does not offer large and cross-border forwarding"); + check(/(?:Large or cross-border coverage|Add nearby or cross-border paths)/.test(scriptText), "UI does not offer large and cross-border forwarding"); check(scriptText.includes("Different repeaters can carry different paths to spread traffic."), "UI does not explain path distribution"); check(scriptText.includes("Neighbouring network paths"), "UI does not expose traffic-evidenced U.S. paths"); check(scriptText.includes("requestedExternalPaths"), "map does not restore neighbouring path selections"); diff --git a/site/404.html b/site/404.html deleted file mode 100644 index 4e97f51..0000000 --- a/site/404.html +++ /dev/null @@ -1,1346 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - MeshCore Canada - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    -
    - -
    - - - - - - -
    - - -
    - -
    - - - - - - -
    -
    - - - -
    -
    -
    - - - - - -
    -
    -
    - - - -
    -
    -
    - - - -
    -
    -
    - - - -
    - -
    - -

    404 - Not found

    - -
    -
    - - - -
    - - - -
    - - - -
    -
    -
    -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/analyzer/builds/mctomqtt/index.html b/site/analyzer/builds/mctomqtt/index.html deleted file mode 100644 index 79deb46..0000000 --- a/site/analyzer/builds/mctomqtt/index.html +++ /dev/null @@ -1,1789 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - MCtoMQTT Script - MeshCore Canada - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - - - - - - -
    - - -
    - -
    - - - - - - -
    -
    - - - -
    -
    -
    - - - - - -
    -
    -
    - - - - - - - -
    - -
    - - - - - - - - -

    MCtoMQTT Script (USB Serial Host)

    -

    Use meshcoretomqtt to bridge a USB-connected MeshCore node (repeater, room server, or any packet-log device) to MQTT brokers. This runs on a Linux or macOS host with the device connected over USB serial.

    -

    What It Does

    -

    The MCtoMQTT service reads packets from a MeshCore device over its serial port and publishes them to one or more MQTT brokers. A config drop-in file tells it where to send the data.

    -

    Prerequisites

    -
      -
    • A MeshCore device with packet logging enabled, connected via USB serial
    • -
    • Linux or macOS host
    • -
    • curl installed
    • -
    • Your 3-character IATA region code (e.g. YOW for Ottawa, YYZ for Toronto)
    • -
    -

    Quick Setup (One-Liner)

    -

    If meshcoretomqtt is already installed, run:

    -
    bash <(curl -fsSL https://live.meshcore.ca/scripts/add-canadaverse-meshcore-broker.sh) --device serial-host
    -
    -

    This creates a config drop-in at /etc/mctomqtt/config.d/20-meshcore-ca.toml pointing at the MeshCore.ca broker pair, then restarts the service.

    -

    Fresh Install

    -

    If meshcoretomqtt is not yet installed, add --install-mctomqtt and the script will run the upstream installer first, then apply the broker config:

    -
    bash <(curl -fsSL https://live.meshcore.ca/scripts/add-canadaverse-meshcore-broker.sh) --device serial-host --install-mctomqtt
    -
    -

    The upstream installer will walk you through serial port selection.

    -

    Specifying Your Region

    -

    Pass your IATA code via the --iata flag or the MESHCORE_CA_IATA environment variable:

    -
    bash <(curl -fsSL https://live.meshcore.ca/scripts/add-canadaverse-meshcore-broker.sh) --device serial-host --iata YOW
    -
    -

    Or:

    -
    MESHCORE_CA_IATA=YOW bash <(curl -fsSL https://live.meshcore.ca/scripts/add-canadaverse-meshcore-broker.sh) --device serial-host
    -
    -

    If omitted, the script will prompt interactively.

    -

    What the Script Creates

    -

    The drop-in config at /etc/mctomqtt/config.d/20-meshcore-ca.toml looks like this:

    -
    [general]
    -iata = "YOW"
    -
    -[[broker]]
    -name = "meshcore-ca-1"
    -enabled = true
    -server = "mqtt1.meshcore.ca"
    -port = 443
    -transport = "websockets"
    -keepalive = 60
    -qos = 0
    -retain = true
    -
    -[broker.tls]
    -enabled = true
    -verify = true
    -
    -[broker.auth]
    -method = "token"
    -audience = "mqtt1.meshcore.ca"
    -
    -[[broker]]
    -name = "meshcore-ca-2"
    -enabled = true
    -server = "mqtt2.meshcore.ca"
    -port = 443
    -transport = "websockets"
    -keepalive = 60
    -qos = 0
    -retain = true
    -
    -[broker.tls]
    -enabled = true
    -verify = true
    -
    -[broker.auth]
    -method = "token"
    -audience = "mqtt2.meshcore.ca"
    -
    -

    Companion Devices (BLE/Serial/TCP)

    -

    For companion radios (not packet-log serial hosts), use the companion path instead:

    -
    -
    -
    -
    bash <(curl -fsSL https://live.meshcore.ca/scripts/add-canadaverse-meshcore-broker.sh) --device companion
    -
    -

    Add --install-packetcapture for a fresh install. The upstream installer will walk you through BLE, serial, or TCP connection setup.

    -
    -
    -
    powershell -NoProfile -ExecutionPolicy Bypass -Command "iwr https://live.meshcore.ca/scripts/add-canadaverse-packetcapture-broker.ps1 -UseBasicParsing | iex"
    -
    -

    Config is stored under %USERPROFILE%\.meshcore-packet-capture\.env.local.

    -
    -
    -
    - - - - - - - - - - - - - - - - - - - - - - - -
    PathManagerConnectionConfig Location
    Serial HostmeshcoretomqttUSB serial/etc/mctomqtt/config.d/20-meshcore-ca.toml
    Companionmeshcore-packet-captureBLE, serial, or TCP~/.meshcore-packet-capture/.env.local
    -
    -

    Windows and Serial Hosts

    -

    There is no upstream meshcoretomqtt Windows installer. Keep packet-log serial hosts on Linux or macOS. The Windows PowerShell helper is for companion radios only.

    -
    -

    Additional Script Options

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    FlagDescription
    --iata CODE3-character region code
    --device TYPEserial-host or companion
    --install-mctomqttInstall meshcoretomqtt if not present
    --install-packetcaptureInstall meshcore-packet-capture if not present
    --no-restartPatch config without restarting the service
    --config-dir PATHOverride config dir (default: /etc/mctomqtt)
    --service NAMEOverride systemd service name (default: mctomqtt)
    -

    Verify

    -

    Once your service is running, head to Verify Observer Status to confirm it's reporting correctly.

    - - - - - - - - - - - - - -
    -
    - - - -
    - - - -
    - - - -
    -
    -
    -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/analyzer/builds/meshcore-ha/index.html b/site/analyzer/builds/meshcore-ha/index.html deleted file mode 100644 index 2652f31..0000000 --- a/site/analyzer/builds/meshcore-ha/index.html +++ /dev/null @@ -1,1780 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - MeshCore-HA - MeshCore Canada - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - - - - - - -
    - - -
    - -
    - - - - - - -
    -
    - - - -
    -
    -
    - - - - - -
    -
    -
    - - - - - - - -
    - -
    - - - - - - - - -

    MeshCore-HA (Home Assistant)

    -

    Add the MeshCore.ca broker pair to your Home Assistant MeshCore integration. This connects your HA instance to the Canadian mesh telemetry network.

    -

    Prerequisites

    -
      -
    • Home Assistant with the MeshCore integration installed
    • -
    • A MeshCore node connected to HA via USB, BLE, or TCP
    • -
    • Your 3-character IATA region code (e.g. YOW for Ottawa, YYZ for Toronto)
    • -
    -

    Setup

    -

    1. Open Broker Settings

    -

    Navigate to:

    -

    Settings > Devices & Services > MeshCore > Configure > Manage MQTT Brokers

    -

    2. Configure the Primary Broker

    -

    Enter the following settings:

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    FieldValue
    Servermqtt1.meshcore.ca
    Port443
    Transportwebsockets
    Use TLSEnabled
    TLS VerifyEnabled
    Username / PasswordLeave blank
    Use MeshCore Auth TokenEnabled
    Token Audiencemqtt1.meshcore.ca
    Auth Token TTLLeave default
    Payload Modepacket
    Status Topicmeshcore/{IATA}/{PUBLIC_KEY}/status
    Packets Topicmeshcore/{IATA}/{PUBLIC_KEY}/packets
    Client ID PrefixOptional
    Owner Public KeyOptional (TLS verified only)
    Owner EmailOptional (TLS verified only)
    -

    3. Configure the Backup Broker

    -

    Add a second broker with the same settings, changing only:

    - - - - - - - - - - - - - - - - - -
    FieldValue
    Servermqtt2.meshcore.ca
    Token Audiencemqtt2.meshcore.ca
    -

    All other fields remain the same as the primary broker.

    -

    4. Set Your Region

    -

    Make sure your IATA region code is set in the integration. The {IATA} placeholder in the topic templates will be replaced with your code (e.g. YOW).

    -

    Quick Reference

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    SettingPrimaryBackup
    Servermqtt1.meshcore.camqtt2.meshcore.ca
    Port443443
    Transportwebsocketswebsockets
    TLSEnabled + VerifiedEnabled + Verified
    AuthMeshCore JWT TokenMeshCore JWT Token
    Audiencemqtt1.meshcore.camqtt2.meshcore.ca
    Payload Modepacketpacket
    -

    Verify

    -

    After saving, head to Verify Observer Status to confirm it's reporting correctly.

    - - - - - - - - - - - - - -
    -
    - - - -
    - - - -
    - - - -
    -
    -
    -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/analyzer/builds/mqtt-firmware/index.html b/site/analyzer/builds/mqtt-firmware/index.html deleted file mode 100644 index 7e28100..0000000 --- a/site/analyzer/builds/mqtt-firmware/index.html +++ /dev/null @@ -1,2644 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - MQTT Firmware - MeshCore Canada - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - - - - - - -
    - - -
    - -
    - - - - - - -
    -
    - - - -
    -
    -
    - - - - - -
    -
    -
    - - - - - - - -
    - -
    - - - - - - - - -

    MQTT Firmware (Standalone Observer)

    -

    Flash standalone MQTT observer firmware with the MeshCore observer flasher. MeshCore Canada no longer hosts separate observer firmware binaries on this page.

    -
    -

    MeshCore Canada presets verified

    -

    The observer firmware currently offered by observer.gessaman.com is built from Adam Gessaman's mqtt-bridge-implementation-flex branch at commit c0c845f5. That branch includes the built-in presets meshcore-ca-1 and meshcore-ca-2, pointing to mqtt1.meshcore.ca and mqtt2.meshcore.ca.

    -
    -

    Flash The Observer

    -
      -
    1. Open observer.gessaman.com.
    2. -
    3. Pick your board under MQTT Observer Firmware.
    4. -
    5. Choose Repeater or Room Server.
    6. -
    7. For a new board or a board you are repurposing, enable Erase device and flash the merged image.
    8. -
    9. When flashing finishes, use Configure via USB for the repeater or room server setup screen, or use Console for CLI setup.
    10. -
    -
    -

    First flash can erase settings

    -

    First flashing observer firmware, especially on boards with a changed partition layout, can wipe stored settings and identity data. Back up an existing device private key before repurposing it.

    -
    -

    Required MeshCore Canada Settings

    -

    Use the repeater or room server setup screen where possible. If a setting is not exposed in the setup screen, use the console on the flasher page and paste the CLI commands below.

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    SettingValue
    Radio presetUSA/Canada (Recommended)
    Raw radio values910.525 MHz / 62.5 kHz / SF7 / CR5
    CLI radio commandset radio 910.525,62.5,7,5
    Path hash mode3-byte advert path hashes: set path.hash.mode 2
    MQTT slot 1meshcore-ca-1
    MQTT slot 2meshcore-ca-2
    IATAA real 3-letter IATA airport code near the observer
    WiFi2.4 GHz network credentials
    -
    -

    IATA codes

    -

    Use a real airport code such as YOW, YYZ, YUL, YVR, or YYC. Do not use placeholders such as XXX or HOME. Do not use CAN as shorthand for Canada; it is an airport code for Guangzhou. See the IATA region code list for Canadian quick choices.

    -
    -

    Command Builder

    -

    Use this builder to create a CLI block for the flasher Console. It runs only in your browser.

    -
    -
    - - - - - - -
    -
    - - -
    -
    -
    - - - - - -

    Manual CLI Reference

    -

    If you prefer to type commands manually, replace YOW, YourWiFiNetwork, and YourWiFiPassword with your own values:

    -
    set name YOW-Repeater-01
    -set radio 910.525,62.5,7,5
    -set path.hash.mode 2
    -set mqtt.iata YOW
    -set wifi.ssid YourWiFiNetwork
    -set wifi.pwd YourWiFiPassword
    -set wifi.powersave none
    -set mqtt1.preset meshcore-ca-1
    -set mqtt2.preset meshcore-ca-2
    -set mqtt3.preset none
    -set mqtt4.preset none
    -set mqtt5.preset none
    -set mqtt6.preset none
    -set mqtt.status on
    -set mqtt.packets on
    -set mqtt.raw off
    -set mqtt.rx on
    -set mqtt.tx advert
    -set bridge.enabled on
    -set repeat on
    -advert
    -reboot
    -
    -

    For an observe-only node that should not repeat mesh traffic, use:

    -
    set repeat off
    -
    -

    Verify

    -

    After the device reboots, reopen the flasher Console and run:

    -
    get wifi.status
    -get mqtt.iata
    -get mqtt1.preset
    -get mqtt2.preset
    -get mqtt.status
    -get path.hash.mode
    -
    -

    Expected broker presets:

    -
    get mqtt1.preset
    -> meshcore-ca-1
    -get mqtt2.preset
    -> meshcore-ca-2
    -
    -

    Once WiFi and MQTT are connected, use Check Your Observer to confirm packets are reaching MeshCore Canada.

    - -
    -
      -
    • -

      Observer Flasher

      -
      -

      Flash MQTT observer firmware and open the serial console.

      -

      observer.gessaman.com

      -
    • -
    • -

      IATA Region Codes

      -
      -

      Pick the real 3-letter airport code nearest to the observer.

      -

      IATA codes

      -
    • -
    • -

      Check Your Observer

      -
      -

      Confirm that the observer is online and reporting.

      -

      verify status

      -
    • -
    -
    - - - - - - - - - - - - - -
    -
    - - - -
    - - - -
    - - - -
    -
    -
    -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/analyzer/builds/pymc/index.html b/site/analyzer/builds/pymc/index.html deleted file mode 100644 index 835bec0..0000000 --- a/site/analyzer/builds/pymc/index.html +++ /dev/null @@ -1,1720 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - PyMC - MeshCore Canada - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - - - - - - -
    - - -
    - -
    - - - - - - -
    -
    - - - -
    -
    -
    - - - - - -
    -
    -
    - - - - - - - -
    - -
    - - - - - - - - -

    PyMC (Python MeshCore)

    -

    Add the MeshCore.ca broker pair to an existing pyMC repeater installation. PyMC connects to your MeshCore device and forwards traffic to MQTT brokers using a YAML config file.

    -

    Prerequisites

    -
      -
    • A working pyMC repeater installation
    • -
    • Your 3-character IATA region code (e.g. YOW for Ottawa, YYZ for Toronto)
    • -
    -

    Configuration

    -

    1. Set Your IATA Code

    -

    In /etc/pymc_repeater/config.yaml, set your region code under the MQTT section:

    -
    mqtt:
    -  iata_code: YOW
    -
    -

    2. Add the Broker Block

    -

    Paste the following under mqtt.brokers in your config file:

    -
    - name: Canadaverse
    -  enabled: true
    -  host: mqtt1.meshcore.ca
    -  port: 443
    -  transport: websockets
    -  format: letsmesh
    -  disallowed_packet_types: []
    -  retain_status: false
    -  tls:
    -    enabled: true
    -    insecure: false
    -  use_jwt_auth: true
    -  audience: mqtt1.meshcore.ca
    -- name: Canadaverse Backup
    -  enabled: true
    -  host: mqtt2.meshcore.ca
    -  port: 443
    -  transport: websockets
    -  format: letsmesh
    -  disallowed_packet_types: []
    -  retain_status: false
    -  tls:
    -    enabled: true
    -    insecure: false
    -  use_jwt_auth: true
    -  audience: mqtt2.meshcore.ca
    -
    -

    3. Optional Fields

    -

    You can also set these optional fields in the mqtt section:

    -
    mqtt:
    -  owner: "your-public-key"
    -  email: "you@example.com"
    -
    -

    4. Restart the Service

    -
    sudo systemctl restart pymc-repeater
    -
    -

    Quick Reference

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    SettingValue
    Config file/etc/pymc_repeater/config.yaml
    Primary brokermqtt1.meshcore.ca
    Backup brokermqtt2.meshcore.ca
    Port443
    Transportwebsockets
    TLSEnabled, verified
    AuthJWT token (use_jwt_auth: true)
    Formatletsmesh
    -

    Verify

    -

    After restarting, head to Verify Observer Status to confirm it's reporting correctly.

    - - - - - - - - - - - - - -
    -
    - - - -
    - - - -
    - - - -
    -
    -
    -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/analyzer/intro/index.html b/site/analyzer/intro/index.html deleted file mode 100644 index 4566f85..0000000 --- a/site/analyzer/intro/index.html +++ /dev/null @@ -1,2004 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - Overview - MeshCore Canada - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - - - - - - -
    - - -
    - -
    - - - - - - -
    -
    - - - -
    -
    -
    - - - - - -
    -
    -
    - - - -
    -
    -
    - - - -
    -
    -
    - - - -
    - -
    - - - - - - - - -

    Analyzer & MQTT Packet Broker

    -

    MeshCore observers capture mesh traffic and publish it to MQTT brokers, feeding telemetry dashboards, maps, and packet inspectors. Pick one of the four observer paths below to start reporting to the MeshCore.ca network.

    -

    Choose Your Observer Path

    -
    -
      -
    • -

      MQTT Firmware

      -
      -

      Flash observer firmware directly onto a board. Connects to WiFi and reports to MQTT with no host computer needed.

      -

      Best for: Heltec V3, V4, and other standalone ESP32 devices.

      -

      MQTT Firmware Guide

      -
    • -
    • -

      MCtoMQTT (USB Host)

      -
      -

      Bridge a USB-connected MeshCore node to MQTT via a Linux or macOS host. Also supports companion radios over BLE, serial, or TCP.

      -

      Best for: Repeaters and room servers with a host machine.

      -

      MCtoMQTT Guide

      -
    • -
    • -

      PyMC

      -
      -

      Add the MeshCore.ca broker pair to an existing pyMC repeater installation via YAML config.

      -

      Best for: Python-based repeater setups.

      -

      PyMC Guide

      -
    • -
    • -

      Home Assistant

      -
      -

      Add MeshCore.ca brokers to the HA MeshCore integration for mesh telemetry on your dashboards.

      -

      Best for: Home Assistant users with a connected MeshCore node.

      -

      MeshCore-HA Guide

      -
    • -
    -
    -
    -
    -
      -
    • -

      Verify Your Observer

      -
      -

      Confirm your observer is online and reporting to the MeshCore.ca network.

      -

      Verify Observer Status

      -
    • -
    • -

      Broker Details

      -
      -

      All paths use the same redundant pair. JWT auth, TLS, no password needed.

      -

      mqtt1.meshcore.ca mqtt2.meshcore.ca (port 443, WSS)

      -
    • -
    -
    -
    -

    IATA Region Codes

    -

    Each observer identifies its region with a 3-character IATA code. Use the airport code nearest to your location. The following codes are supported on live.meshcore.ca today.

    -
    -Ontario - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    CodeRegion
    YYZToronto (Pearson)
    YTZToronto (Billy Bishop)
    YOWOttawa
    YHMHamilton
    YKFKitchener / Waterloo
    YXULondon
    YOOOshawa
    YKZButtonville / Markham
    YAMSault Ste. Marie
    YQTThunder Bay
    YSBSudbury
    YTSTimmins
    YQGWindsor
    YYBNorth Bay
    YGKKingston
    YPQPeterborough
    YHDDryden
    YPLPickle Lake
    YNDGatineau (Ottawa area)
    -
    -
    -Quebec - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    CodeRegion
    YULMontreal (Trudeau)
    YMXMontreal (Mirabel)
    YQBQuebec City
    YBGBagotville / Saguenay
    YVOVal-d'Or
    YHUMontreal (St-Hubert)
    YRJRoberval
    YGLLa Grande Riviere
    YSCSherbrooke
    YTQTasiujaq
    YUYRouyn-Noranda
    YZVSept-Iles
    YGPGaspe
    YRQTrois-Rivieres
    -
    -
    -British Columbia - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    CodeRegion
    YVRVancouver
    YYJVictoria
    YXXAbbotsford / Fraser Valley
    YLWKelowna
    YXSPrince George
    YPRPrince Rupert
    YXTTerrace
    YQQComox / Courtenay
    YCDNanaimo
    YYDSmithers
    YDQDawson Creek
    YXJFort St. John
    YYFPenticton
    YCGCastlegar
    YKAKamloops
    YXCCranbrook
    YBCBaie-Comeau
    -
    -
    -Alberta - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    CodeRegion
    YYCCalgary
    YEGEdmonton
    YMMFort McMurray
    YQUGrande Prairie
    YQLLethbridge
    YXHMedicine Hat
    -
    -
    -Saskatchewan - - - - - - - - - - - - - - - - - - - - - -
    CodeRegion
    YQRRegina
    YXESaskatoon
    YPAPrince Albert
    -
    -
    -Manitoba - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    CodeRegion
    YWGWinnipeg
    YBRBrandon
    YTHThompson
    YDNDauphin
    YPGPortage la Prairie
    -
    -
    -New Brunswick - - - - - - - - - - - - - - - - - - - - - - - - - -
    CodeRegion
    YFCFredericton
    YSJSaint John
    YQMMoncton
    ZBFBathurst
    -
    -
    -Nova Scotia - - - - - - - - - - - - - - - - - - - - - -
    CodeRegion
    YHZHalifax
    YQYSydney
    YQIYarmouth
    -
    -
    -Prince Edward Island - - - - - - - - - - - - - -
    CodeRegion
    YYGCharlottetown
    -
    -
    -Newfoundland and Labrador - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    CodeRegion
    YYTSt. John's
    YQXGander
    YDFDeer Lake
    YYRGoose Bay
    YWKWabush
    -
    -
    -Territories (YT / NT / NU) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    CodeRegion
    YXYWhitehorse (Yukon)
    YZFYellowknife (NWT)
    YFBIqaluit (Nunavut)
    YEVInuvik (NWT)
    YHYHay River (NWT)
    -
    - - - - - - - - - - - - - -
    -
    - - - -
    - - - -
    - - - -
    -
    -
    -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/analyzer/verify/index.html b/site/analyzer/verify/index.html deleted file mode 100644 index 048cbd0..0000000 --- a/site/analyzer/verify/index.html +++ /dev/null @@ -1,1704 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - Verify Observer Status - MeshCore Canada - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - - - - - - -
    - - -
    - -
    - - - - - - -
    -
    - - - -
    -
    -
    - - - - - -
    -
    -
    - - - - - - - -
    - -
    - - - - - - - - -

    Verify Observer Status

    -

    After setting up your observer using any of the four paths (MQTT Firmware, MCtoMQTT, PyMC, or Home Assistant), use the links below to confirm it's online and reporting.

    -

    Check Your Observer

    -
    -
      -
    • -

      CoreScope Observers

      -
      -

      See all connected observers and their current status.

      -

      View Observers

      -
    • -
    • -

      CoreScope Packets

      -
      -

      Watch live packet traffic flowing through observers in real time.

      -

      View Packets

      -
    • -
    • -

      MeshCore Map

      -
      -

      See observers and nodes plotted on the map.

      -

      View Map

      -
    • -
    -
    -

    Your observer should appear within a few minutes of coming online.

    -

    Troubleshooting

    -

    If your observer doesn't show up, work through these checks based on your setup path.

    -

    MQTT Firmware

    -

    Run these in the device's admin CLI:

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    CommandWhat to check
    get wifi.statusShould show connected to your 2.4 GHz network
    get mqtt.statusShould show an active broker connection
    get mqtt.iataShould return your 3-character IATA code
    get mqtt1.presetShould show custom (not none)
    get mqtt2.presetShould show custom (not none)
    get nameShould return the node name you set
    -
    -Full verify command block -
    get name
    -get mqtt.origin
    -get mqtt.iata
    -get wifi.status
    -get mqtt.packets
    -get bridge.enabled
    -get mqtt.rx
    -get mqtt.tx
    -get mqtt.status
    -get mqtt1.preset
    -get mqtt2.preset
    -get mqtt3.preset
    -
    -
    -

    MCtoMQTT / Companion (USB Host)

    -

    Check that the systemd service is running:

    -
    # Serial host
    -sudo systemctl status mctomqtt
    -
    -# Companion
    -sudo systemctl status meshcore-capture
    -
    -

    If the service is running but nothing appears, check the config drop-in:

    -
    # Serial host
    -cat /etc/mctomqtt/config.d/20-meshcore-ca.toml
    -
    -# Companion
    -cat ~/.meshcore-packet-capture/.env.local
    -
    -

    Confirm the broker hosts are mqtt1.meshcore.ca and mqtt2.meshcore.ca.

    -

    PyMC

    -
    sudo systemctl status pymc-repeater
    -
    -

    Check that your mqtt.iata_code is set and the broker block is present in /etc/pymc_repeater/config.yaml.

    -

    Home Assistant

    -

    Go to Settings > Devices & Services > MeshCore > Configure > Manage MQTT Brokers and confirm both brokers show as connected. Make sure your IATA code is set in the integration.

    -

    Still Not Working?

    -

    If everything looks correct but your observer still doesn't appear, double check that your device has internet access and can reach mqtt1.meshcore.ca on port 443. Firewalls or network restrictions on outbound WebSocket connections are the most common blocker.

    - - - - - - - - - - - - - -
    -
    - - - -
    - - - -
    - - - -
    -
    -
    -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/assets/MeshCore-Canada-Favicon.png b/site/assets/MeshCore-Canada-Favicon.png deleted file mode 100644 index f4bfec6779ec85c19395389c1fbe2d77c866dd7c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 274340 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�xS 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

    K=JsPh5@8!_ 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

  • Ma^`pfUa;tC8K9!Q)jVc`vMk%Jc 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 I+cE6_05^!?jsO4v diff --git a/site/assets/images/favicon.png b/site/assets/images/favicon.png deleted file mode 100644 index 1cf13b9f9d978896599290a74f77d5dbe7d1655c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1870 zcmV-U2eJ5xP)Gc)JR9QMau)O=X#!i9;T z37kk-upj^(fsR36MHs_+1RCI)NNu9}lD0S{B^g8PN?Ww(5|~L#Ng*g{WsqleV}|#l zz8@ri&cTzw_h33bHI+12+kK6WN$h#n5cD8OQt`5kw6p~9H3()bUQ8OS4Q4HTQ=1Ol z_JAocz`fLbT2^{`8n~UAo=#AUOf=SOq4pYkt;XbC&f#7lb$*7=$na!mWCQ`dBQsO0 zLFBSPj*N?#u5&pf2t4XjEGH|=pPQ8xh7tpx;US5Cx_Ju;!O`ya-yF`)b%TEt5>eP1ZX~}sjjA%FJF?h7cX8=b!DZl<6%Cv z*G0uvvU+vmnpLZ2paivG-(cd*y3$hCIcsZcYOGh{$&)A6*XX&kXZd3G8m)G$Zz-LV z^GF3VAW^Mdv!)4OM8EgqRiz~*Cji;uzl2uC9^=8I84vNp;ltJ|q-*uQwGp2ma6cY7 z;`%`!9UXO@fr&Ebapfs34OmS9^u6$)bJxrucutf>`dKPKT%%*d3XlFVKunp9 zasduxjrjs>f8V=D|J=XNZp;_Zy^WgQ$9WDjgY=z@stwiEBm9u5*|34&1Na8BMjjgf3+SHcr`5~>oz1Y?SW^=K z^bTyO6>Gar#P_W2gEMwq)ot3; zREHn~U&Dp0l6YT0&k-wLwYjb?5zGK`W6S2v+K>AM(95m2C20L|3m~rN8dprPr@t)5lsk9Hu*W z?pS990s;Ez=+Rj{x7p``4>+c0G5^pYnB1^!TL=(?HLHZ+HicG{~4F1d^5Awl_2!1jICM-!9eoLhbbT^;yHcefyTAaqRcY zmuctDopPT!%k+}x%lZRKnzykr2}}XfG_ne?nRQO~?%hkzo;@RN{P6o`&mMUWBYMTe z6i8ChtjX&gXl`nvrU>jah)2iNM%JdjqoaeaU%yVn!^70x-flljp6Q5tK}5}&X8&&G zX3fpb3E(!rH=zVI_9Gjl45w@{(ITqngWFe7@9{mX;tO25Z_8 zQHEpI+FkTU#4xu>RkN>b3Tnc3UpWzPXWm#o55GKF09j^Mh~)K7{QqbO_~(@CVq! zS<8954|P8mXN2MRs86xZ&Q4EfM@JB94b=(YGuk)s&^jiSF=t3*oNK3`rD{H`yQ?d; ztE=laAUoZx5?RC8*WKOj`%LXEkgDd>&^Q4M^z`%u0rg-It=hLCVsq!Z%^6eB-OvOT zFZ28TN&cRmgU}Elrnk43)!>Z1FCPL2K$7}gwzIc48NX}#!A1BpJP?#v5wkNprhV** z?Cpalt1oH&{r!o3eSKc&ap)iz2BTn_VV`4>9M^b3;(YY}4>#ML6{~(4mH+?%07*qo IM6N<$f(jP3KmY&$ diff --git a/site/assets/javascripts/bundle.79ae519e.min.js b/site/assets/javascripts/bundle.79ae519e.min.js deleted file mode 100644 index 3df3e5e..0000000 --- a/site/assets/javascripts/bundle.79ae519e.min.js +++ /dev/null @@ -1,16 +0,0 @@ -"use strict";(()=>{var Zi=Object.create;var _r=Object.defineProperty;var ea=Object.getOwnPropertyDescriptor;var ta=Object.getOwnPropertyNames,Bt=Object.getOwnPropertySymbols,ra=Object.getPrototypeOf,Ar=Object.prototype.hasOwnProperty,bo=Object.prototype.propertyIsEnumerable;var ho=(e,t,r)=>t in e?_r(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r,P=(e,t)=>{for(var r in t||(t={}))Ar.call(t,r)&&ho(e,r,t[r]);if(Bt)for(var r of Bt(t))bo.call(t,r)&&ho(e,r,t[r]);return e};var vo=(e,t)=>{var r={};for(var o in e)Ar.call(e,o)&&t.indexOf(o)<0&&(r[o]=e[o]);if(e!=null&&Bt)for(var o of Bt(e))t.indexOf(o)<0&&bo.call(e,o)&&(r[o]=e[o]);return r};var Cr=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var oa=(e,t,r,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of ta(t))!Ar.call(e,n)&&n!==r&&_r(e,n,{get:()=>t[n],enumerable:!(o=ea(t,n))||o.enumerable});return e};var $t=(e,t,r)=>(r=e!=null?Zi(ra(e)):{},oa(t||!e||!e.__esModule?_r(r,"default",{value:e,enumerable:!0}):r,e));var go=(e,t,r)=>new Promise((o,n)=>{var i=c=>{try{a(r.next(c))}catch(p){n(p)}},s=c=>{try{a(r.throw(c))}catch(p){n(p)}},a=c=>c.done?o(c.value):Promise.resolve(c.value).then(i,s);a((r=r.apply(e,t)).next())});var xo=Cr((kr,yo)=>{(function(e,t){typeof kr=="object"&&typeof yo!="undefined"?t():typeof define=="function"&&define.amd?define(t):t()})(kr,(function(){"use strict";function e(r){var o=!0,n=!1,i=null,s={text:!0,search:!0,url:!0,tel:!0,email:!0,password:!0,number:!0,date:!0,month:!0,week:!0,time:!0,datetime:!0,"datetime-local":!0};function a(k){return!!(k&&k!==document&&k.nodeName!=="HTML"&&k.nodeName!=="BODY"&&"classList"in k&&"contains"in k.classList)}function c(k){var ut=k.type,je=k.tagName;return!!(je==="INPUT"&&s[ut]&&!k.readOnly||je==="TEXTAREA"&&!k.readOnly||k.isContentEditable)}function p(k){k.classList.contains("focus-visible")||(k.classList.add("focus-visible"),k.setAttribute("data-focus-visible-added",""))}function l(k){k.hasAttribute("data-focus-visible-added")&&(k.classList.remove("focus-visible"),k.removeAttribute("data-focus-visible-added"))}function f(k){k.metaKey||k.altKey||k.ctrlKey||(a(r.activeElement)&&p(r.activeElement),o=!0)}function u(k){o=!1}function d(k){a(k.target)&&(o||c(k.target))&&p(k.target)}function v(k){a(k.target)&&(k.target.classList.contains("focus-visible")||k.target.hasAttribute("data-focus-visible-added"))&&(n=!0,window.clearTimeout(i),i=window.setTimeout(function(){n=!1},100),l(k.target))}function S(k){document.visibilityState==="hidden"&&(n&&(o=!0),X())}function X(){document.addEventListener("mousemove",ee),document.addEventListener("mousedown",ee),document.addEventListener("mouseup",ee),document.addEventListener("pointermove",ee),document.addEventListener("pointerdown",ee),document.addEventListener("pointerup",ee),document.addEventListener("touchmove",ee),document.addEventListener("touchstart",ee),document.addEventListener("touchend",ee)}function re(){document.removeEventListener("mousemove",ee),document.removeEventListener("mousedown",ee),document.removeEventListener("mouseup",ee),document.removeEventListener("pointermove",ee),document.removeEventListener("pointerdown",ee),document.removeEventListener("pointerup",ee),document.removeEventListener("touchmove",ee),document.removeEventListener("touchstart",ee),document.removeEventListener("touchend",ee)}function ee(k){k.target.nodeName&&k.target.nodeName.toLowerCase()==="html"||(o=!1,re())}document.addEventListener("keydown",f,!0),document.addEventListener("mousedown",u,!0),document.addEventListener("pointerdown",u,!0),document.addEventListener("touchstart",u,!0),document.addEventListener("visibilitychange",S,!0),X(),r.addEventListener("focus",d,!0),r.addEventListener("blur",v,!0),r.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&r.host?r.host.setAttribute("data-js-focus-visible",""):r.nodeType===Node.DOCUMENT_NODE&&(document.documentElement.classList.add("js-focus-visible"),document.documentElement.setAttribute("data-js-focus-visible",""))}if(typeof window!="undefined"&&typeof document!="undefined"){window.applyFocusVisiblePolyfill=e;var t;try{t=new CustomEvent("focus-visible-polyfill-ready")}catch(r){t=document.createEvent("CustomEvent"),t.initCustomEvent("focus-visible-polyfill-ready",!1,!1,{})}window.dispatchEvent(t)}typeof document!="undefined"&&e(document)}))});var ro=Cr((jy,Rn)=>{"use strict";/*! - * escape-html - * Copyright(c) 2012-2013 TJ Holowaychuk - * Copyright(c) 2015 Andreas Lubbe - * Copyright(c) 2015 Tiancheng "Timothy" Gu - * MIT Licensed - */var qa=/["'&<>]/;Rn.exports=Ka;function Ka(e){var t=""+e,r=qa.exec(t);if(!r)return t;var o,n="",i=0,s=0;for(i=r.index;i{/*! - * clipboard.js v2.0.11 - * https://clipboardjs.com/ - * - * Licensed MIT © Zeno Rocha - */(function(t,r){typeof Nt=="object"&&typeof io=="object"?io.exports=r():typeof define=="function"&&define.amd?define([],r):typeof Nt=="object"?Nt.ClipboardJS=r():t.ClipboardJS=r()})(Nt,function(){return(function(){var e={686:(function(o,n,i){"use strict";i.d(n,{default:function(){return Xi}});var s=i(279),a=i.n(s),c=i(370),p=i.n(c),l=i(817),f=i.n(l);function u(q){try{return document.execCommand(q)}catch(C){return!1}}var d=function(C){var _=f()(C);return u("cut"),_},v=d;function S(q){var C=document.documentElement.getAttribute("dir")==="rtl",_=document.createElement("textarea");_.style.fontSize="12pt",_.style.border="0",_.style.padding="0",_.style.margin="0",_.style.position="absolute",_.style[C?"right":"left"]="-9999px";var D=window.pageYOffset||document.documentElement.scrollTop;return _.style.top="".concat(D,"px"),_.setAttribute("readonly",""),_.value=q,_}var X=function(C,_){var D=S(C);_.container.appendChild(D);var N=f()(D);return u("copy"),D.remove(),N},re=function(C){var _=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body},D="";return typeof C=="string"?D=X(C,_):C instanceof HTMLInputElement&&!["text","search","url","tel","password"].includes(C==null?void 0:C.type)?D=X(C.value,_):(D=f()(C),u("copy")),D},ee=re;function k(q){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?k=function(_){return typeof _}:k=function(_){return _&&typeof Symbol=="function"&&_.constructor===Symbol&&_!==Symbol.prototype?"symbol":typeof _},k(q)}var ut=function(){var C=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},_=C.action,D=_===void 0?"copy":_,N=C.container,G=C.target,We=C.text;if(D!=="copy"&&D!=="cut")throw new Error('Invalid "action" value, use either "copy" or "cut"');if(G!==void 0)if(G&&k(G)==="object"&&G.nodeType===1){if(D==="copy"&&G.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if(D==="cut"&&(G.hasAttribute("readonly")||G.hasAttribute("disabled")))throw new Error(`Invalid "target" attribute. You can't cut text from elements with "readonly" or "disabled" attributes`)}else throw new Error('Invalid "target" value, use a valid Element');if(We)return ee(We,{container:N});if(G)return D==="cut"?v(G):ee(G,{container:N})},je=ut;function R(q){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?R=function(_){return typeof _}:R=function(_){return _&&typeof Symbol=="function"&&_.constructor===Symbol&&_!==Symbol.prototype?"symbol":typeof _},R(q)}function se(q,C){if(!(q instanceof C))throw new TypeError("Cannot call a class as a function")}function ce(q,C){for(var _=0;_0&&arguments[0]!==void 0?arguments[0]:{};this.action=typeof N.action=="function"?N.action:this.defaultAction,this.target=typeof N.target=="function"?N.target:this.defaultTarget,this.text=typeof N.text=="function"?N.text:this.defaultText,this.container=R(N.container)==="object"?N.container:document.body}},{key:"listenClick",value:function(N){var G=this;this.listener=p()(N,"click",function(We){return G.onClick(We)})}},{key:"onClick",value:function(N){var G=N.delegateTarget||N.currentTarget,We=this.action(G)||"copy",Yt=je({action:We,container:this.container,target:this.target(G),text:this.text(G)});this.emit(Yt?"success":"error",{action:We,text:Yt,trigger:G,clearSelection:function(){G&&G.focus(),window.getSelection().removeAllRanges()}})}},{key:"defaultAction",value:function(N){return Mr("action",N)}},{key:"defaultTarget",value:function(N){var G=Mr("target",N);if(G)return document.querySelector(G)}},{key:"defaultText",value:function(N){return Mr("text",N)}},{key:"destroy",value:function(){this.listener.destroy()}}],[{key:"copy",value:function(N){var G=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body};return ee(N,G)}},{key:"cut",value:function(N){return v(N)}},{key:"isSupported",value:function(){var N=arguments.length>0&&arguments[0]!==void 0?arguments[0]:["copy","cut"],G=typeof N=="string"?[N]:N,We=!!document.queryCommandSupported;return G.forEach(function(Yt){We=We&&!!document.queryCommandSupported(Yt)}),We}}]),_})(a()),Xi=Ji}),828:(function(o){var n=9;if(typeof Element!="undefined"&&!Element.prototype.matches){var i=Element.prototype;i.matches=i.matchesSelector||i.mozMatchesSelector||i.msMatchesSelector||i.oMatchesSelector||i.webkitMatchesSelector}function s(a,c){for(;a&&a.nodeType!==n;){if(typeof a.matches=="function"&&a.matches(c))return a;a=a.parentNode}}o.exports=s}),438:(function(o,n,i){var s=i(828);function a(l,f,u,d,v){var S=p.apply(this,arguments);return l.addEventListener(u,S,v),{destroy:function(){l.removeEventListener(u,S,v)}}}function c(l,f,u,d,v){return typeof l.addEventListener=="function"?a.apply(null,arguments):typeof u=="function"?a.bind(null,document).apply(null,arguments):(typeof l=="string"&&(l=document.querySelectorAll(l)),Array.prototype.map.call(l,function(S){return a(S,f,u,d,v)}))}function p(l,f,u,d){return function(v){v.delegateTarget=s(v.target,f),v.delegateTarget&&d.call(l,v)}}o.exports=c}),879:(function(o,n){n.node=function(i){return i!==void 0&&i instanceof HTMLElement&&i.nodeType===1},n.nodeList=function(i){var s=Object.prototype.toString.call(i);return i!==void 0&&(s==="[object NodeList]"||s==="[object HTMLCollection]")&&"length"in i&&(i.length===0||n.node(i[0]))},n.string=function(i){return typeof i=="string"||i instanceof String},n.fn=function(i){var s=Object.prototype.toString.call(i);return s==="[object Function]"}}),370:(function(o,n,i){var s=i(879),a=i(438);function c(u,d,v){if(!u&&!d&&!v)throw new Error("Missing required arguments");if(!s.string(d))throw new TypeError("Second argument must be a String");if(!s.fn(v))throw new TypeError("Third argument must be a Function");if(s.node(u))return p(u,d,v);if(s.nodeList(u))return l(u,d,v);if(s.string(u))return f(u,d,v);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList")}function p(u,d,v){return u.addEventListener(d,v),{destroy:function(){u.removeEventListener(d,v)}}}function l(u,d,v){return Array.prototype.forEach.call(u,function(S){S.addEventListener(d,v)}),{destroy:function(){Array.prototype.forEach.call(u,function(S){S.removeEventListener(d,v)})}}}function f(u,d,v){return a(document.body,u,d,v)}o.exports=c}),817:(function(o){function n(i){var s;if(i.nodeName==="SELECT")i.focus(),s=i.value;else if(i.nodeName==="INPUT"||i.nodeName==="TEXTAREA"){var a=i.hasAttribute("readonly");a||i.setAttribute("readonly",""),i.select(),i.setSelectionRange(0,i.value.length),a||i.removeAttribute("readonly"),s=i.value}else{i.hasAttribute("contenteditable")&&i.focus();var c=window.getSelection(),p=document.createRange();p.selectNodeContents(i),c.removeAllRanges(),c.addRange(p),s=c.toString()}return s}o.exports=n}),279:(function(o){function n(){}n.prototype={on:function(i,s,a){var c=this.e||(this.e={});return(c[i]||(c[i]=[])).push({fn:s,ctx:a}),this},once:function(i,s,a){var c=this;function p(){c.off(i,p),s.apply(a,arguments)}return p._=s,this.on(i,p,a)},emit:function(i){var s=[].slice.call(arguments,1),a=((this.e||(this.e={}))[i]||[]).slice(),c=0,p=a.length;for(c;c0&&i[i.length-1])&&(p[0]===6||p[0]===2)){r=0;continue}if(p[0]===3&&(!i||p[1]>i[0]&&p[1]=e.length&&(e=void 0),{value:e&&e[o++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function K(e,t){var r=typeof Symbol=="function"&&e[Symbol.iterator];if(!r)return e;var o=r.call(e),n,i=[],s;try{for(;(t===void 0||t-- >0)&&!(n=o.next()).done;)i.push(n.value)}catch(a){s={error:a}}finally{try{n&&!n.done&&(r=o.return)&&r.call(o)}finally{if(s)throw s.error}}return i}function B(e,t,r){if(r||arguments.length===2)for(var o=0,n=t.length,i;o1||c(d,S)})},v&&(n[d]=v(n[d])))}function c(d,v){try{p(o[d](v))}catch(S){u(i[0][3],S)}}function p(d){d.value instanceof dt?Promise.resolve(d.value.v).then(l,f):u(i[0][2],d)}function l(d){c("next",d)}function f(d){c("throw",d)}function u(d,v){d(v),i.shift(),i.length&&c(i[0][0],i[0][1])}}function To(e){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var t=e[Symbol.asyncIterator],r;return t?t.call(e):(e=typeof Oe=="function"?Oe(e):e[Symbol.iterator](),r={},o("next"),o("throw"),o("return"),r[Symbol.asyncIterator]=function(){return this},r);function o(i){r[i]=e[i]&&function(s){return new Promise(function(a,c){s=e[i](s),n(a,c,s.done,s.value)})}}function n(i,s,a,c){Promise.resolve(c).then(function(p){i({value:p,done:a})},s)}}function I(e){return typeof e=="function"}function yt(e){var t=function(o){Error.call(o),o.stack=new Error().stack},r=e(t);return r.prototype=Object.create(Error.prototype),r.prototype.constructor=r,r}var Jt=yt(function(e){return function(r){e(this),this.message=r?r.length+` errors occurred during unsubscription: -`+r.map(function(o,n){return n+1+") "+o.toString()}).join(` - `):"",this.name="UnsubscriptionError",this.errors=r}});function Ze(e,t){if(e){var r=e.indexOf(t);0<=r&&e.splice(r,1)}}var qe=(function(){function e(t){this.initialTeardown=t,this.closed=!1,this._parentage=null,this._finalizers=null}return e.prototype.unsubscribe=function(){var t,r,o,n,i;if(!this.closed){this.closed=!0;var s=this._parentage;if(s)if(this._parentage=null,Array.isArray(s))try{for(var a=Oe(s),c=a.next();!c.done;c=a.next()){var p=c.value;p.remove(this)}}catch(S){t={error:S}}finally{try{c&&!c.done&&(r=a.return)&&r.call(a)}finally{if(t)throw t.error}}else s.remove(this);var l=this.initialTeardown;if(I(l))try{l()}catch(S){i=S instanceof Jt?S.errors:[S]}var f=this._finalizers;if(f){this._finalizers=null;try{for(var u=Oe(f),d=u.next();!d.done;d=u.next()){var v=d.value;try{So(v)}catch(S){i=i!=null?i:[],S instanceof Jt?i=B(B([],K(i)),K(S.errors)):i.push(S)}}}catch(S){o={error:S}}finally{try{d&&!d.done&&(n=u.return)&&n.call(u)}finally{if(o)throw o.error}}}if(i)throw new Jt(i)}},e.prototype.add=function(t){var r;if(t&&t!==this)if(this.closed)So(t);else{if(t instanceof e){if(t.closed||t._hasParent(this))return;t._addParent(this)}(this._finalizers=(r=this._finalizers)!==null&&r!==void 0?r:[]).push(t)}},e.prototype._hasParent=function(t){var r=this._parentage;return r===t||Array.isArray(r)&&r.includes(t)},e.prototype._addParent=function(t){var r=this._parentage;this._parentage=Array.isArray(r)?(r.push(t),r):r?[r,t]:t},e.prototype._removeParent=function(t){var r=this._parentage;r===t?this._parentage=null:Array.isArray(r)&&Ze(r,t)},e.prototype.remove=function(t){var r=this._finalizers;r&&Ze(r,t),t instanceof e&&t._removeParent(this)},e.EMPTY=(function(){var t=new e;return t.closed=!0,t})(),e})();var $r=qe.EMPTY;function Xt(e){return e instanceof qe||e&&"closed"in e&&I(e.remove)&&I(e.add)&&I(e.unsubscribe)}function So(e){I(e)?e():e.unsubscribe()}var De={onUnhandledError:null,onStoppedNotification:null,Promise:void 0,useDeprecatedSynchronousErrorHandling:!1,useDeprecatedNextContext:!1};var xt={setTimeout:function(e,t){for(var r=[],o=2;o0},enumerable:!1,configurable:!0}),t.prototype._trySubscribe=function(r){return this._throwIfClosed(),e.prototype._trySubscribe.call(this,r)},t.prototype._subscribe=function(r){return this._throwIfClosed(),this._checkFinalizedStatuses(r),this._innerSubscribe(r)},t.prototype._innerSubscribe=function(r){var o=this,n=this,i=n.hasError,s=n.isStopped,a=n.observers;return i||s?$r:(this.currentObservers=null,a.push(r),new qe(function(){o.currentObservers=null,Ze(a,r)}))},t.prototype._checkFinalizedStatuses=function(r){var o=this,n=o.hasError,i=o.thrownError,s=o.isStopped;n?r.error(i):s&&r.complete()},t.prototype.asObservable=function(){var r=new F;return r.source=this,r},t.create=function(r,o){return new Ho(r,o)},t})(F);var Ho=(function(e){ie(t,e);function t(r,o){var n=e.call(this)||this;return n.destination=r,n.source=o,n}return t.prototype.next=function(r){var o,n;(n=(o=this.destination)===null||o===void 0?void 0:o.next)===null||n===void 0||n.call(o,r)},t.prototype.error=function(r){var o,n;(n=(o=this.destination)===null||o===void 0?void 0:o.error)===null||n===void 0||n.call(o,r)},t.prototype.complete=function(){var r,o;(o=(r=this.destination)===null||r===void 0?void 0:r.complete)===null||o===void 0||o.call(r)},t.prototype._subscribe=function(r){var o,n;return(n=(o=this.source)===null||o===void 0?void 0:o.subscribe(r))!==null&&n!==void 0?n:$r},t})(T);var jr=(function(e){ie(t,e);function t(r){var o=e.call(this)||this;return o._value=r,o}return Object.defineProperty(t.prototype,"value",{get:function(){return this.getValue()},enumerable:!1,configurable:!0}),t.prototype._subscribe=function(r){var o=e.prototype._subscribe.call(this,r);return!o.closed&&r.next(this._value),o},t.prototype.getValue=function(){var r=this,o=r.hasError,n=r.thrownError,i=r._value;if(o)throw n;return this._throwIfClosed(),i},t.prototype.next=function(r){e.prototype.next.call(this,this._value=r)},t})(T);var Rt={now:function(){return(Rt.delegate||Date).now()},delegate:void 0};var It=(function(e){ie(t,e);function t(r,o,n){r===void 0&&(r=1/0),o===void 0&&(o=1/0),n===void 0&&(n=Rt);var i=e.call(this)||this;return i._bufferSize=r,i._windowTime=o,i._timestampProvider=n,i._buffer=[],i._infiniteTimeWindow=!0,i._infiniteTimeWindow=o===1/0,i._bufferSize=Math.max(1,r),i._windowTime=Math.max(1,o),i}return t.prototype.next=function(r){var o=this,n=o.isStopped,i=o._buffer,s=o._infiniteTimeWindow,a=o._timestampProvider,c=o._windowTime;n||(i.push(r),!s&&i.push(a.now()+c)),this._trimBuffer(),e.prototype.next.call(this,r)},t.prototype._subscribe=function(r){this._throwIfClosed(),this._trimBuffer();for(var o=this._innerSubscribe(r),n=this,i=n._infiniteTimeWindow,s=n._buffer,a=s.slice(),c=0;c0?e.prototype.schedule.call(this,r,o):(this.delay=o,this.state=r,this.scheduler.flush(this),this)},t.prototype.execute=function(r,o){return o>0||this.closed?e.prototype.execute.call(this,r,o):this._execute(r,o)},t.prototype.requestAsyncId=function(r,o,n){return n===void 0&&(n=0),n!=null&&n>0||n==null&&this.delay>0?e.prototype.requestAsyncId.call(this,r,o,n):(r.flush(this),0)},t})(St);var Ro=(function(e){ie(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t})(Ot);var Dr=new Ro(Po);var Io=(function(e){ie(t,e);function t(r,o){var n=e.call(this,r,o)||this;return n.scheduler=r,n.work=o,n}return t.prototype.requestAsyncId=function(r,o,n){return n===void 0&&(n=0),n!==null&&n>0?e.prototype.requestAsyncId.call(this,r,o,n):(r.actions.push(this),r._scheduled||(r._scheduled=Tt.requestAnimationFrame(function(){return r.flush(void 0)})))},t.prototype.recycleAsyncId=function(r,o,n){var i;if(n===void 0&&(n=0),n!=null?n>0:this.delay>0)return e.prototype.recycleAsyncId.call(this,r,o,n);var s=r.actions;o!=null&&o===r._scheduled&&((i=s[s.length-1])===null||i===void 0?void 0:i.id)!==o&&(Tt.cancelAnimationFrame(o),r._scheduled=void 0)},t})(St);var Fo=(function(e){ie(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t.prototype.flush=function(r){this._active=!0;var o;r?o=r.id:(o=this._scheduled,this._scheduled=void 0);var n=this.actions,i;r=r||n.shift();do if(i=r.execute(r.state,r.delay))break;while((r=n[0])&&r.id===o&&n.shift());if(this._active=!1,i){for(;(r=n[0])&&r.id===o&&n.shift();)r.unsubscribe();throw i}},t})(Ot);var ye=new Fo(Io);var y=new F(function(e){return e.complete()});function tr(e){return e&&I(e.schedule)}function Vr(e){return e[e.length-1]}function pt(e){return I(Vr(e))?e.pop():void 0}function Fe(e){return tr(Vr(e))?e.pop():void 0}function rr(e,t){return typeof Vr(e)=="number"?e.pop():t}var Lt=(function(e){return e&&typeof e.length=="number"&&typeof e!="function"});function or(e){return I(e==null?void 0:e.then)}function nr(e){return I(e[wt])}function ir(e){return Symbol.asyncIterator&&I(e==null?void 0:e[Symbol.asyncIterator])}function ar(e){return new TypeError("You provided "+(e!==null&&typeof e=="object"?"an invalid object":"'"+e+"'")+" where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.")}function fa(){return typeof Symbol!="function"||!Symbol.iterator?"@@iterator":Symbol.iterator}var sr=fa();function cr(e){return I(e==null?void 0:e[sr])}function pr(e){return wo(this,arguments,function(){var r,o,n,i;return Gt(this,function(s){switch(s.label){case 0:r=e.getReader(),s.label=1;case 1:s.trys.push([1,,9,10]),s.label=2;case 2:return[4,dt(r.read())];case 3:return o=s.sent(),n=o.value,i=o.done,i?[4,dt(void 0)]:[3,5];case 4:return[2,s.sent()];case 5:return[4,dt(n)];case 6:return[4,s.sent()];case 7:return s.sent(),[3,2];case 8:return[3,10];case 9:return r.releaseLock(),[7];case 10:return[2]}})})}function lr(e){return I(e==null?void 0:e.getReader)}function U(e){if(e instanceof F)return e;if(e!=null){if(nr(e))return ua(e);if(Lt(e))return da(e);if(or(e))return ha(e);if(ir(e))return jo(e);if(cr(e))return ba(e);if(lr(e))return va(e)}throw ar(e)}function ua(e){return new F(function(t){var r=e[wt]();if(I(r.subscribe))return r.subscribe(t);throw new TypeError("Provided object does not correctly implement Symbol.observable")})}function da(e){return new F(function(t){for(var r=0;r=2;return function(o){return o.pipe(e?g(function(n,i){return e(n,i,o)}):be,Ee(1),r?Qe(t):tn(function(){return new fr}))}}function Yr(e){return e<=0?function(){return y}:E(function(t,r){var o=[];t.subscribe(w(r,function(n){o.push(n),e=2,!0))}function le(e){e===void 0&&(e={});var t=e.connector,r=t===void 0?function(){return new T}:t,o=e.resetOnError,n=o===void 0?!0:o,i=e.resetOnComplete,s=i===void 0?!0:i,a=e.resetOnRefCountZero,c=a===void 0?!0:a;return function(p){var l,f,u,d=0,v=!1,S=!1,X=function(){f==null||f.unsubscribe(),f=void 0},re=function(){X(),l=u=void 0,v=S=!1},ee=function(){var k=l;re(),k==null||k.unsubscribe()};return E(function(k,ut){d++,!S&&!v&&X();var je=u=u!=null?u:r();ut.add(function(){d--,d===0&&!S&&!v&&(f=Br(ee,c))}),je.subscribe(ut),!l&&d>0&&(l=new bt({next:function(R){return je.next(R)},error:function(R){S=!0,X(),f=Br(re,n,R),je.error(R)},complete:function(){v=!0,X(),f=Br(re,s),je.complete()}}),U(k).subscribe(l))})(p)}}function Br(e,t){for(var r=[],o=2;oe.next(document)),e}function M(e,t=document){return Array.from(t.querySelectorAll(e))}function j(e,t=document){let r=ue(e,t);if(typeof r=="undefined")throw new ReferenceError(`Missing element: expected "${e}" to be present`);return r}function ue(e,t=document){return t.querySelector(e)||void 0}function Ne(){var e,t,r,o;return(o=(r=(t=(e=document.activeElement)==null?void 0:e.shadowRoot)==null?void 0:t.activeElement)!=null?r:document.activeElement)!=null?o:void 0}var Ra=L(h(document.body,"focusin"),h(document.body,"focusout")).pipe(Ae(1),Q(void 0),m(()=>Ne()||document.body),Z(1));function Ye(e){return Ra.pipe(m(t=>e.contains(t)),Y())}function it(e,t){return H(()=>L(h(e,"mouseenter").pipe(m(()=>!0)),h(e,"mouseleave").pipe(m(()=>!1))).pipe(t?jt(r=>He(+!r*t)):be,Q(e.matches(":hover"))))}function sn(e,t){if(typeof t=="string"||typeof t=="number")e.innerHTML+=t.toString();else if(t instanceof Node)e.appendChild(t);else if(Array.isArray(t))for(let r of t)sn(e,r)}function x(e,t,...r){let o=document.createElement(e);if(t)for(let n of Object.keys(t))typeof t[n]!="undefined"&&(typeof t[n]!="boolean"?o.setAttribute(n,t[n]):o.setAttribute(n,""));for(let n of r)sn(o,n);return o}function br(e){if(e>999){let t=+((e-950)%1e3>99);return`${((e+1e-6)/1e3).toFixed(t)}k`}else return e.toString()}function _t(e){let t=x("script",{src:e});return H(()=>(document.head.appendChild(t),L(h(t,"load"),h(t,"error").pipe(b(()=>Nr(()=>new ReferenceError(`Invalid script: ${e}`))))).pipe(m(()=>{}),A(()=>document.head.removeChild(t)),Ee(1))))}var cn=new T,Ia=H(()=>typeof ResizeObserver=="undefined"?_t("https://unpkg.com/resize-observer-polyfill"):$(void 0)).pipe(m(()=>new ResizeObserver(e=>e.forEach(t=>cn.next(t)))),b(e=>L(tt,$(e)).pipe(A(()=>e.disconnect()))),Z(1));function de(e){return{width:e.offsetWidth,height:e.offsetHeight}}function Le(e){let t=e;for(;t.clientWidth===0&&t.parentElement;)t=t.parentElement;return Ia.pipe(O(r=>r.observe(t)),b(r=>cn.pipe(g(o=>o.target===t),A(()=>r.unobserve(t)))),m(()=>de(e)),Q(de(e)))}function At(e){return{width:e.scrollWidth,height:e.scrollHeight}}function vr(e){let t=e.parentElement;for(;t&&(e.scrollWidth<=t.scrollWidth&&e.scrollHeight<=t.scrollHeight);)t=(e=t).parentElement;return t?e:void 0}function pn(e){let t=[],r=e.parentElement;for(;r;)(e.clientWidth>r.clientWidth||e.clientHeight>r.clientHeight)&&t.push(r),r=(e=r).parentElement;return t.length===0&&t.push(document.documentElement),t}function Be(e){return{x:e.offsetLeft,y:e.offsetTop}}function ln(e){let t=e.getBoundingClientRect();return{x:t.x+window.scrollX,y:t.y+window.scrollY}}function mn(e){return L(h(window,"load"),h(window,"resize")).pipe($e(0,ye),m(()=>Be(e)),Q(Be(e)))}function gr(e){return{x:e.scrollLeft,y:e.scrollTop}}function Ge(e){return L(h(e,"scroll"),h(window,"scroll"),h(window,"resize")).pipe($e(0,ye),m(()=>gr(e)),Q(gr(e)))}var fn=new T,Fa=H(()=>$(new IntersectionObserver(e=>{for(let t of e)fn.next(t)},{threshold:0}))).pipe(b(e=>L(tt,$(e)).pipe(A(()=>e.disconnect()))),Z(1));function mt(e){return Fa.pipe(O(t=>t.observe(e)),b(t=>fn.pipe(g(({target:r})=>r===e),A(()=>t.unobserve(e)),m(({isIntersecting:r})=>r))))}function un(e,t=16){return Ge(e).pipe(m(({y:r})=>{let o=de(e),n=At(e);return r>=n.height-o.height-t}),Y())}var yr={drawer:j("[data-md-toggle=drawer]"),search:j("[data-md-toggle=search]")};function dn(e){return yr[e].checked}function at(e,t){yr[e].checked!==t&&yr[e].click()}function Je(e){let t=yr[e];return h(t,"change").pipe(m(()=>t.checked),Q(t.checked))}function ja(e,t){switch(e.constructor){case HTMLInputElement:return e.type==="radio"?/^Arrow/.test(t):!0;case HTMLSelectElement:case HTMLTextAreaElement:return!0;default:return e.isContentEditable}}function Ua(){return L(h(window,"compositionstart").pipe(m(()=>!0)),h(window,"compositionend").pipe(m(()=>!1))).pipe(Q(!1))}function hn(){let e=h(window,"keydown").pipe(g(t=>!(t.metaKey||t.ctrlKey)),m(t=>({mode:dn("search")?"search":"global",type:t.key,claim(){t.preventDefault(),t.stopPropagation()}})),g(({mode:t,type:r})=>{if(t==="global"){let o=Ne();if(typeof o!="undefined")return!ja(o,r)}return!0}),le());return Ua().pipe(b(t=>t?y:e))}function we(){return new URL(location.href)}function st(e,t=!1){if(V("navigation.instant")&&!t){let r=x("a",{href:e.href});document.body.appendChild(r),r.click(),r.remove()}else location.href=e.href}function bn(){return new T}function vn(){return location.hash.slice(1)}function gn(e){let t=x("a",{href:e});t.addEventListener("click",r=>r.stopPropagation()),t.click()}function Zr(e){return L(h(window,"hashchange"),e).pipe(m(vn),Q(vn()),g(t=>t.length>0),Z(1))}function yn(e){return Zr(e).pipe(m(t=>ue(`[id="${t}"]`)),g(t=>typeof t!="undefined"))}function Wt(e){let t=matchMedia(e);return ur(r=>t.addListener(()=>r(t.matches))).pipe(Q(t.matches))}function xn(){let e=matchMedia("print");return L(h(window,"beforeprint").pipe(m(()=>!0)),h(window,"afterprint").pipe(m(()=>!1))).pipe(Q(e.matches))}function eo(e,t){return e.pipe(b(r=>r?t():y))}function to(e,t){return new F(r=>{let o=new XMLHttpRequest;return o.open("GET",`${e}`),o.responseType="blob",o.addEventListener("load",()=>{o.status>=200&&o.status<300?(r.next(o.response),r.complete()):r.error(new Error(o.statusText))}),o.addEventListener("error",()=>{r.error(new Error("Network error"))}),o.addEventListener("abort",()=>{r.complete()}),typeof(t==null?void 0:t.progress$)!="undefined"&&(o.addEventListener("progress",n=>{var i;if(n.lengthComputable)t.progress$.next(n.loaded/n.total*100);else{let s=(i=o.getResponseHeader("Content-Length"))!=null?i:0;t.progress$.next(n.loaded/+s*100)}}),t.progress$.next(5)),o.send(),()=>o.abort()})}function ze(e,t){return to(e,t).pipe(b(r=>r.text()),m(r=>JSON.parse(r)),Z(1))}function xr(e,t){let r=new DOMParser;return to(e,t).pipe(b(o=>o.text()),m(o=>r.parseFromString(o,"text/html")),Z(1))}function En(e,t){let r=new DOMParser;return to(e,t).pipe(b(o=>o.text()),m(o=>r.parseFromString(o,"text/xml")),Z(1))}function wn(){return{x:Math.max(0,scrollX),y:Math.max(0,scrollY)}}function Tn(){return L(h(window,"scroll",{passive:!0}),h(window,"resize",{passive:!0})).pipe(m(wn),Q(wn()))}function Sn(){return{width:innerWidth,height:innerHeight}}function On(){return h(window,"resize",{passive:!0}).pipe(m(Sn),Q(Sn()))}function Ln(){return z([Tn(),On()]).pipe(m(([e,t])=>({offset:e,size:t})),Z(1))}function Er(e,{viewport$:t,header$:r}){let o=t.pipe(ne("size")),n=z([o,r]).pipe(m(()=>Be(e)));return z([r,t,n]).pipe(m(([{height:i},{offset:s,size:a},{x:c,y:p}])=>({offset:{x:s.x-c,y:s.y-p+i},size:a})))}function Wa(e){return h(e,"message",t=>t.data)}function Da(e){let t=new T;return t.subscribe(r=>e.postMessage(r)),t}function Mn(e,t=new Worker(e)){let r=Wa(t),o=Da(t),n=new T;n.subscribe(o);let i=o.pipe(oe(),ae(!0));return n.pipe(oe(),Ve(r.pipe(W(i))),le())}var Va=j("#__config"),Ct=JSON.parse(Va.textContent);Ct.base=`${new URL(Ct.base,we())}`;function Te(){return Ct}function V(e){return Ct.features.includes(e)}function Me(e,t){return typeof t!="undefined"?Ct.translations[e].replace("#",t.toString()):Ct.translations[e]}function Ce(e,t=document){return j(`[data-md-component=${e}]`,t)}function me(e,t=document){return M(`[data-md-component=${e}]`,t)}function Na(e){let t=j(".md-typeset > :first-child",e);return h(t,"click",{once:!0}).pipe(m(()=>j(".md-typeset",e)),m(r=>({hash:__md_hash(r.innerHTML)})))}function _n(e){if(!V("announce.dismiss")||!e.childElementCount)return y;if(!e.hidden){let t=j(".md-typeset",e);__md_hash(t.innerHTML)===__md_get("__announce")&&(e.hidden=!0)}return H(()=>{let t=new T;return t.subscribe(({hash:r})=>{e.hidden=!0,__md_set("__announce",r)}),Na(e).pipe(O(r=>t.next(r)),A(()=>t.complete()),m(r=>P({ref:e},r)))})}function za(e,{target$:t}){return t.pipe(m(r=>({hidden:r!==e})))}function An(e,t){let r=new T;return r.subscribe(({hidden:o})=>{e.hidden=o}),za(e,t).pipe(O(o=>r.next(o)),A(()=>r.complete()),m(o=>P({ref:e},o)))}function Dt(e,t){return t==="inline"?x("div",{class:"md-tooltip md-tooltip--inline",id:e,role:"tooltip"},x("div",{class:"md-tooltip__inner md-typeset"})):x("div",{class:"md-tooltip",id:e,role:"tooltip"},x("div",{class:"md-tooltip__inner md-typeset"}))}function wr(...e){return x("div",{class:"md-tooltip2",role:"dialog"},x("div",{class:"md-tooltip2__inner md-typeset"},e))}function Cn(...e){return x("div",{class:"md-tooltip2",role:"tooltip"},x("div",{class:"md-tooltip2__inner md-typeset"},e))}function kn(e,t){if(t=t?`${t}_annotation_${e}`:void 0,t){let r=t?`#${t}`:void 0;return x("aside",{class:"md-annotation",tabIndex:0},Dt(t),x("a",{href:r,class:"md-annotation__index",tabIndex:-1},x("span",{"data-md-annotation-id":e})))}else return x("aside",{class:"md-annotation",tabIndex:0},Dt(t),x("span",{class:"md-annotation__index",tabIndex:-1},x("span",{"data-md-annotation-id":e})))}function Hn(e){return x("button",{class:"md-code__button",title:Me("clipboard.copy"),"data-clipboard-target":`#${e} > code`,"data-md-type":"copy"})}function $n(){return x("button",{class:"md-code__button",title:"Toggle line selection","data-md-type":"select"})}function Pn(){return x("nav",{class:"md-code__nav"})}var In=$t(ro());function oo(e,t){let r=t&2,o=t&1,n=Object.keys(e.terms).filter(c=>!e.terms[c]).reduce((c,p)=>[...c,x("del",null,(0,In.default)(p))," "],[]).slice(0,-1),i=Te(),s=new URL(e.location,i.base);V("search.highlight")&&s.searchParams.set("h",Object.entries(e.terms).filter(([,c])=>c).reduce((c,[p])=>`${c} ${p}`.trim(),""));let{tags:a}=Te();return x("a",{href:`${s}`,class:"md-search-result__link",tabIndex:-1},x("article",{class:"md-search-result__article md-typeset","data-md-score":e.score.toFixed(2)},r>0&&x("div",{class:"md-search-result__icon md-icon"}),r>0&&x("h1",null,e.title),r<=0&&x("h2",null,e.title),o>0&&e.text.length>0&&e.text,e.tags&&x("nav",{class:"md-tags"},e.tags.map(c=>{let p=a?c in a?`md-tag-icon md-tag--${a[c]}`:"md-tag-icon":"";return x("span",{class:`md-tag ${p}`},c)})),o>0&&n.length>0&&x("p",{class:"md-search-result__terms"},Me("search.result.term.missing"),": ",...n)))}function Fn(e){let t=e[0].score,r=[...e],o=Te(),n=r.findIndex(l=>!`${new URL(l.location,o.base)}`.includes("#")),[i]=r.splice(n,1),s=r.findIndex(l=>l.scoreoo(l,1)),...c.length?[x("details",{class:"md-search-result__more"},x("summary",{tabIndex:-1},x("div",null,c.length>0&&c.length===1?Me("search.result.more.one"):Me("search.result.more.other",c.length))),...c.map(l=>oo(l,1)))]:[]];return x("li",{class:"md-search-result__item"},p)}function jn(e){return x("ul",{class:"md-source__facts"},Object.entries(e).map(([t,r])=>x("li",{class:`md-source__fact md-source__fact--${t}`},typeof r=="number"?br(r):r)))}function no(e){let t=`tabbed-control tabbed-control--${e}`;return x("div",{class:t,hidden:!0},x("button",{class:"tabbed-button",tabIndex:-1,"aria-hidden":"true"}))}function Un(e){return x("div",{class:"md-typeset__scrollwrap"},x("div",{class:"md-typeset__table"},e))}function Qa(e){var o;let t=Te(),r=new URL(`../${e.version}/`,t.base);return x("li",{class:"md-version__item"},x("a",{href:`${r}`,class:"md-version__link"},e.title,((o=t.version)==null?void 0:o.alias)&&e.aliases.length>0&&x("span",{class:"md-version__alias"},e.aliases[0])))}function Wn(e,t){var o;let r=Te();return e=e.filter(n=>{var i;return!((i=n.properties)!=null&&i.hidden)}),x("div",{class:"md-version"},x("button",{class:"md-version__current","aria-label":Me("select.version")},t.title,((o=r.version)==null?void 0:o.alias)&&t.aliases.length>0&&x("span",{class:"md-version__alias"},t.aliases[0])),x("ul",{class:"md-version__list"},e.map(Qa)))}var Ya=0;function Ba(e,t=250){let r=z([Ye(e),it(e,t)]).pipe(m(([n,i])=>n||i),Y()),o=H(()=>pn(e)).pipe(J(Ge),gt(1),Pe(r),m(()=>ln(e)));return r.pipe(Re(n=>n),b(()=>z([r,o])),m(([n,i])=>({active:n,offset:i})),le())}function Vt(e,t,r=250){let{content$:o,viewport$:n}=t,i=`__tooltip2_${Ya++}`;return H(()=>{let s=new T,a=new jr(!1);s.pipe(oe(),ae(!1)).subscribe(a);let c=a.pipe(jt(l=>He(+!l*250,Dr)),Y(),b(l=>l?o:y),O(l=>l.id=i),le());z([s.pipe(m(({active:l})=>l)),c.pipe(b(l=>it(l,250)),Q(!1))]).pipe(m(l=>l.some(f=>f))).subscribe(a);let p=a.pipe(g(l=>l),te(c,n),m(([l,f,{size:u}])=>{let d=e.getBoundingClientRect(),v=d.width/2;if(f.role==="tooltip")return{x:v,y:8+d.height};if(d.y>=u.height/2){let{height:S}=de(f);return{x:v,y:-16-S}}else return{x:v,y:16+d.height}}));return z([c,s,p]).subscribe(([l,{offset:f},u])=>{l.style.setProperty("--md-tooltip-host-x",`${f.x}px`),l.style.setProperty("--md-tooltip-host-y",`${f.y}px`),l.style.setProperty("--md-tooltip-x",`${u.x}px`),l.style.setProperty("--md-tooltip-y",`${u.y}px`),l.classList.toggle("md-tooltip2--top",u.y<0),l.classList.toggle("md-tooltip2--bottom",u.y>=0)}),a.pipe(g(l=>l),te(c,(l,f)=>f),g(l=>l.role==="tooltip")).subscribe(l=>{let f=de(j(":scope > *",l));l.style.setProperty("--md-tooltip-width",`${f.width}px`),l.style.setProperty("--md-tooltip-tail","0px")}),a.pipe(Y(),xe(ye),te(c)).subscribe(([l,f])=>{f.classList.toggle("md-tooltip2--active",l)}),z([a.pipe(g(l=>l)),c]).subscribe(([l,f])=>{f.role==="dialog"?(e.setAttribute("aria-controls",i),e.setAttribute("aria-haspopup","dialog")):e.setAttribute("aria-describedby",i)}),a.pipe(g(l=>!l)).subscribe(()=>{e.removeAttribute("aria-controls"),e.removeAttribute("aria-describedby"),e.removeAttribute("aria-haspopup")}),Ba(e,r).pipe(O(l=>s.next(l)),A(()=>s.complete()),m(l=>P({ref:e},l)))})}function Xe(e,{viewport$:t},r=document.body){return Vt(e,{content$:new F(o=>{let n=e.title,i=Cn(n);return o.next(i),e.removeAttribute("title"),r.append(i),()=>{i.remove(),e.setAttribute("title",n)}}),viewport$:t},0)}function Ga(e,t){let r=H(()=>z([mn(e),Ge(t)])).pipe(m(([{x:o,y:n},i])=>{let{width:s,height:a}=de(e);return{x:o-i.x+s/2,y:n-i.y+a/2}}));return Ye(e).pipe(b(o=>r.pipe(m(n=>({active:o,offset:n})),Ee(+!o||1/0))))}function Dn(e,t,{target$:r}){let[o,n]=Array.from(e.children);return H(()=>{let i=new T,s=i.pipe(oe(),ae(!0));return i.subscribe({next({offset:a}){e.style.setProperty("--md-tooltip-x",`${a.x}px`),e.style.setProperty("--md-tooltip-y",`${a.y}px`)},complete(){e.style.removeProperty("--md-tooltip-x"),e.style.removeProperty("--md-tooltip-y")}}),mt(e).pipe(W(s)).subscribe(a=>{e.toggleAttribute("data-md-visible",a)}),L(i.pipe(g(({active:a})=>a)),i.pipe(Ae(250),g(({active:a})=>!a))).subscribe({next({active:a}){a?e.prepend(o):o.remove()},complete(){e.prepend(o)}}),i.pipe($e(16,ye)).subscribe(({active:a})=>{o.classList.toggle("md-tooltip--active",a)}),i.pipe(gt(125,ye),g(()=>!!e.offsetParent),m(()=>e.offsetParent.getBoundingClientRect()),m(({x:a})=>a)).subscribe({next(a){a?e.style.setProperty("--md-tooltip-0",`${-a}px`):e.style.removeProperty("--md-tooltip-0")},complete(){e.style.removeProperty("--md-tooltip-0")}}),h(n,"click").pipe(W(s),g(a=>!(a.metaKey||a.ctrlKey))).subscribe(a=>{a.stopPropagation(),a.preventDefault()}),h(n,"mousedown").pipe(W(s),te(i)).subscribe(([a,{active:c}])=>{var p;if(a.button!==0||a.metaKey||a.ctrlKey)a.preventDefault();else if(c){a.preventDefault();let l=e.parentElement.closest(".md-annotation");l instanceof HTMLElement?l.focus():(p=Ne())==null||p.blur()}}),r.pipe(W(s),g(a=>a===o),nt(125)).subscribe(()=>e.focus()),Ga(e,t).pipe(O(a=>i.next(a)),A(()=>i.complete()),m(a=>P({ref:e},a)))})}function Ja(e){let t=Te();if(e.tagName!=="CODE")return[e];let r=[".c",".c1",".cm"];if(t.annotate&&typeof t.annotate=="object"){let o=e.closest("[class|=language]");if(o)for(let n of Array.from(o.classList)){if(!n.startsWith("language-"))continue;let[,i]=n.split("-");i in t.annotate&&r.push(...t.annotate[i])}}return M(r.join(", "),e)}function Xa(e){let t=[];for(let r of Ja(e)){let o=[],n=document.createNodeIterator(r,NodeFilter.SHOW_TEXT);for(let i=n.nextNode();i;i=n.nextNode())o.push(i);for(let i of o){let s;for(;s=/(\(\d+\))(!)?/.exec(i.textContent);){let[,a,c]=s;if(typeof c=="undefined"){let p=i.splitText(s.index);i=p.splitText(a.length),t.push(p)}else{i.textContent=a,t.push(i);break}}}}return t}function Vn(e,t){t.append(...Array.from(e.childNodes))}function Tr(e,t,{target$:r,print$:o}){let n=t.closest("[id]"),i=n==null?void 0:n.id,s=new Map;for(let a of Xa(t)){let[,c]=a.textContent.match(/\((\d+)\)/);ue(`:scope > li:nth-child(${c})`,e)&&(s.set(c,kn(c,i)),a.replaceWith(s.get(c)))}return s.size===0?y:H(()=>{let a=new T,c=a.pipe(oe(),ae(!0)),p=[];for(let[l,f]of s)p.push([j(".md-typeset",f),j(`:scope > li:nth-child(${l})`,e)]);return o.pipe(W(c)).subscribe(l=>{e.hidden=!l,e.classList.toggle("md-annotation-list",l);for(let[f,u]of p)l?Vn(f,u):Vn(u,f)}),L(...[...s].map(([,l])=>Dn(l,t,{target$:r}))).pipe(A(()=>a.complete()),le())})}function Nn(e){if(e.nextElementSibling){let t=e.nextElementSibling;if(t.tagName==="OL")return t;if(t.tagName==="P"&&!t.children.length)return Nn(t)}}function zn(e,t){return H(()=>{let r=Nn(e);return typeof r!="undefined"?Tr(r,e,t):y})}var Kn=$t(ao());var Za=0,qn=L(h(window,"keydown").pipe(m(()=>!0)),L(h(window,"keyup"),h(window,"contextmenu")).pipe(m(()=>!1))).pipe(Q(!1),Z(1));function Qn(e){if(e.nextElementSibling){let t=e.nextElementSibling;if(t.tagName==="OL")return t;if(t.tagName==="P"&&!t.children.length)return Qn(t)}}function es(e){return Le(e).pipe(m(({width:t})=>({scrollable:At(e).width>t})),ne("scrollable"))}function Yn(e,t){let{matches:r}=matchMedia("(hover)"),o=H(()=>{let n=new T,i=n.pipe(Yr(1));n.subscribe(({scrollable:d})=>{d&&r?e.setAttribute("tabindex","0"):e.removeAttribute("tabindex")});let s=[],a=e.closest("pre"),c=a.closest("[id]"),p=c?c.id:Za++;a.id=`__code_${p}`;let l=[],f=e.closest(".highlight");if(f instanceof HTMLElement){let d=Qn(f);if(typeof d!="undefined"&&(f.classList.contains("annotate")||V("content.code.annotate"))){let v=Tr(d,e,t);l.push(Le(f).pipe(W(i),m(({width:S,height:X})=>S&&X),Y(),b(S=>S?v:y)))}}let u=M(":scope > span[id]",e);if(u.length&&(e.classList.add("md-code__content"),e.closest(".select")||V("content.code.select")&&!e.closest(".no-select"))){let d=+u[0].id.split("-").pop(),v=$n();s.push(v),V("content.tooltips")&&l.push(Xe(v,{viewport$}));let S=h(v,"click").pipe(Ut(R=>!R,!1),O(()=>v.blur()),le());S.subscribe(R=>{v.classList.toggle("md-code__button--active",R)});let X=fe(u).pipe(J(R=>it(R).pipe(m(se=>[R,se]))));S.pipe(b(R=>R?X:y)).subscribe(([R,se])=>{let ce=ue(".hll.select",R);if(ce&&!se)ce.replaceWith(...Array.from(ce.childNodes));else if(!ce&&se){let he=document.createElement("span");he.className="hll select",he.append(...Array.from(R.childNodes).slice(1)),R.append(he)}});let re=fe(u).pipe(J(R=>h(R,"mousedown").pipe(O(se=>se.preventDefault()),m(()=>R)))),ee=S.pipe(b(R=>R?re:y),te(qn),m(([R,se])=>{var he;let ce=u.indexOf(R)+d;if(se===!1)return[ce,ce];{let Se=M(".hll",e).map(Ue=>u.indexOf(Ue.parentElement)+d);return(he=window.getSelection())==null||he.removeAllRanges(),[Math.min(ce,...Se),Math.max(ce,...Se)]}})),k=Zr(y).pipe(g(R=>R.startsWith(`__codelineno-${p}-`)));k.subscribe(R=>{let[,,se]=R.split("-"),ce=se.split(":").map(Se=>+Se-d+1);ce.length===1&&ce.push(ce[0]);for(let Se of M(".hll:not(.select)",e))Se.replaceWith(...Array.from(Se.childNodes));let he=u.slice(ce[0]-1,ce[1]);for(let Se of he){let Ue=document.createElement("span");Ue.className="hll",Ue.append(...Array.from(Se.childNodes).slice(1)),Se.append(Ue)}}),k.pipe(Ee(1),xe(pe)).subscribe(R=>{if(R.includes(":")){let se=document.getElementById(R.split(":")[0]);se&&setTimeout(()=>{let ce=se,he=-64;for(;ce!==document.body;)he+=ce.offsetTop,ce=ce.offsetParent;window.scrollTo({top:he})},1)}});let je=fe(M('a[href^="#__codelineno"]',f)).pipe(J(R=>h(R,"click").pipe(O(se=>se.preventDefault()),m(()=>R)))).pipe(W(i),te(qn),m(([R,se])=>{let he=+j(`[id="${R.hash.slice(1)}"]`).parentElement.id.split("-").pop();if(se===!1)return[he,he];{let Se=M(".hll",e).map(Ue=>+Ue.parentElement.id.split("-").pop());return[Math.min(he,...Se),Math.max(he,...Se)]}}));L(ee,je).subscribe(R=>{let se=`#__codelineno-${p}-`;R[0]===R[1]?se+=R[0]:se+=`${R[0]}:${R[1]}`,history.replaceState({},"",se),window.dispatchEvent(new HashChangeEvent("hashchange",{newURL:window.location.origin+window.location.pathname+se,oldURL:window.location.href}))})}if(Kn.default.isSupported()&&(e.closest(".copy")||V("content.code.copy")&&!e.closest(".no-copy"))){let d=Hn(a.id);s.push(d),V("content.tooltips")&&l.push(Xe(d,{viewport$}))}if(s.length){let d=Pn();d.append(...s),a.insertBefore(d,e)}return es(e).pipe(O(d=>n.next(d)),A(()=>n.complete()),m(d=>P({ref:e},d)),Ve(L(...l).pipe(W(i))))});return V("content.lazy")?mt(e).pipe(g(n=>n),Ee(1),b(()=>o)):o}function ts(e,{target$:t,print$:r}){let o=!0;return L(t.pipe(m(n=>n.closest("details:not([open])")),g(n=>e===n),m(()=>({action:"open",reveal:!0}))),r.pipe(g(n=>n||!o),O(()=>o=e.open),m(n=>({action:n?"open":"close"}))))}function Bn(e,t){return H(()=>{let r=new T;return r.subscribe(({action:o,reveal:n})=>{e.toggleAttribute("open",o==="open"),n&&e.scrollIntoView()}),ts(e,t).pipe(O(o=>r.next(o)),A(()=>r.complete()),m(o=>P({ref:e},o)))})}var Gn=0;function rs(e){let t=document.createElement("h3");t.innerHTML=e.innerHTML;let r=[t],o=e.nextElementSibling;for(;o&&!(o instanceof HTMLHeadingElement);)r.push(o),o=o.nextElementSibling;return r}function os(e,t){for(let r of M("[href], [src]",e))for(let o of["href","src"]){let n=r.getAttribute(o);if(n&&!/^(?:[a-z]+:)?\/\//i.test(n)){r[o]=new URL(r.getAttribute(o),t).toString();break}}for(let r of M("[name^=__], [for]",e))for(let o of["id","for","name"]){let n=r.getAttribute(o);n&&r.setAttribute(o,`${n}$preview_${Gn}`)}return Gn++,$(e)}function Jn(e,t){let{sitemap$:r}=t;if(!(e instanceof HTMLAnchorElement))return y;if(!(V("navigation.instant.preview")||e.hasAttribute("data-preview")))return y;e.removeAttribute("title");let o=z([Ye(e),it(e)]).pipe(m(([i,s])=>i||s),Y(),g(i=>i));return rt([r,o]).pipe(b(([i])=>{let s=new URL(e.href);return s.search=s.hash="",i.has(`${s}`)?$(s):y}),b(i=>xr(i).pipe(b(s=>os(s,i)))),b(i=>{let s=e.hash?`article [id="${e.hash.slice(1)}"]`:"article h1",a=ue(s,i);return typeof a=="undefined"?y:$(rs(a))})).pipe(b(i=>{let s=new F(a=>{let c=wr(...i);return a.next(c),document.body.append(c),()=>c.remove()});return Vt(e,P({content$:s},t))}))}var Xn=".node circle,.node ellipse,.node path,.node polygon,.node rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}marker{fill:var(--md-mermaid-edge-color)!important}.edgeLabel .label rect{fill:#0000}.flowchartTitleText{fill:var(--md-mermaid-label-fg-color)}.label{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.label foreignObject{line-height:normal;overflow:visible}.label div .edgeLabel{color:var(--md-mermaid-label-fg-color)}.edgeLabel,.edgeLabel p,.label div .edgeLabel{background-color:var(--md-mermaid-label-bg-color)}.edgeLabel,.edgeLabel p{fill:var(--md-mermaid-label-bg-color);color:var(--md-mermaid-edge-color)}.edgePath .path,.flowchart-link{stroke:var(--md-mermaid-edge-color)}.edgePath .arrowheadPath{fill:var(--md-mermaid-edge-color);stroke:none}.cluster rect{fill:var(--md-default-fg-color--lightest);stroke:var(--md-default-fg-color--lighter)}.cluster span{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}g #flowchart-circleEnd,g #flowchart-circleStart,g #flowchart-crossEnd,g #flowchart-crossStart,g #flowchart-pointEnd,g #flowchart-pointStart{stroke:none}.classDiagramTitleText{fill:var(--md-mermaid-label-fg-color)}g.classGroup line,g.classGroup rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}g.classGroup text{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.classLabel .box{fill:var(--md-mermaid-label-bg-color);background-color:var(--md-mermaid-label-bg-color);opacity:1}.classLabel .label{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.node .divider{stroke:var(--md-mermaid-node-fg-color)}.relation{stroke:var(--md-mermaid-edge-color)}.cardinality{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.cardinality text{fill:inherit!important}defs marker.marker.composition.class path,defs marker.marker.dependency.class path,defs marker.marker.extension.class path{fill:var(--md-mermaid-edge-color)!important;stroke:var(--md-mermaid-edge-color)!important}defs marker.marker.aggregation.class path{fill:var(--md-mermaid-label-bg-color)!important;stroke:var(--md-mermaid-edge-color)!important}.statediagramTitleText{fill:var(--md-mermaid-label-fg-color)}g.stateGroup rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}g.stateGroup .state-title{fill:var(--md-mermaid-label-fg-color)!important;font-family:var(--md-mermaid-font-family)}g.stateGroup .composit{fill:var(--md-mermaid-label-bg-color)}.nodeLabel,.nodeLabel p{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}a .nodeLabel{text-decoration:underline}.node circle.state-end,.node circle.state-start,.start-state{fill:var(--md-mermaid-edge-color);stroke:none}.end-state-inner,.end-state-outer{fill:var(--md-mermaid-edge-color)}.end-state-inner,.node circle.state-end{stroke:var(--md-mermaid-label-bg-color)}.transition{stroke:var(--md-mermaid-edge-color)}[id^=state-fork] rect,[id^=state-join] rect{fill:var(--md-mermaid-edge-color)!important;stroke:none!important}.statediagram-cluster.statediagram-cluster .inner{fill:var(--md-default-bg-color)}.statediagram-cluster rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}.statediagram-state rect.divider{fill:var(--md-default-fg-color--lightest);stroke:var(--md-default-fg-color--lighter)}defs #statediagram-barbEnd{stroke:var(--md-mermaid-edge-color)}[id^=entity] path,[id^=entity] rect{fill:var(--md-default-bg-color)}.relationshipLine{stroke:var(--md-mermaid-edge-color)}defs .marker.oneOrMore.er *,defs .marker.onlyOne.er *,defs .marker.zeroOrMore.er *,defs .marker.zeroOrOne.er *{stroke:var(--md-mermaid-edge-color)!important}text:not([class]):last-child{fill:var(--md-mermaid-label-fg-color)}.actor{fill:var(--md-mermaid-sequence-actor-bg-color);stroke:var(--md-mermaid-sequence-actor-border-color)}text.actor>tspan{fill:var(--md-mermaid-sequence-actor-fg-color);font-family:var(--md-mermaid-font-family)}line{stroke:var(--md-mermaid-sequence-actor-line-color)}.actor-man circle,.actor-man line{fill:var(--md-mermaid-sequence-actorman-bg-color);stroke:var(--md-mermaid-sequence-actorman-line-color)}.messageLine0,.messageLine1{stroke:var(--md-mermaid-sequence-message-line-color)}.note{fill:var(--md-mermaid-sequence-note-bg-color);stroke:var(--md-mermaid-sequence-note-border-color)}.loopText,.loopText>tspan,.messageText,.noteText>tspan{stroke:none;font-family:var(--md-mermaid-font-family)!important}.messageText{fill:var(--md-mermaid-sequence-message-fg-color)}.loopText,.loopText>tspan{fill:var(--md-mermaid-sequence-loop-fg-color)}.noteText>tspan{fill:var(--md-mermaid-sequence-note-fg-color)}#arrowhead path{fill:var(--md-mermaid-sequence-message-line-color);stroke:none}.loopLine{fill:var(--md-mermaid-sequence-loop-bg-color);stroke:var(--md-mermaid-sequence-loop-border-color)}.labelBox{fill:var(--md-mermaid-sequence-label-bg-color);stroke:none}.labelText,.labelText>span{fill:var(--md-mermaid-sequence-label-fg-color);font-family:var(--md-mermaid-font-family)}.sequenceNumber{fill:var(--md-mermaid-sequence-number-fg-color)}rect.rect{fill:var(--md-mermaid-sequence-box-bg-color);stroke:none}rect.rect+text.text{fill:var(--md-mermaid-sequence-box-fg-color)}defs #sequencenumber{fill:var(--md-mermaid-sequence-number-bg-color)!important}";var so,is=0;function as(){return typeof mermaid=="undefined"||mermaid instanceof Element?_t("https://unpkg.com/mermaid@11/dist/mermaid.min.js"):$(void 0)}function Zn(e){return e.classList.remove("mermaid"),so||(so=as().pipe(O(()=>mermaid.initialize({startOnLoad:!1,themeCSS:Xn,sequence:{actorFontSize:"16px",messageFontSize:"16px",noteFontSize:"16px"}})),m(()=>{}),Z(1))),so.subscribe(()=>go(null,null,function*(){e.classList.add("mermaid");let t=`__mermaid_${is++}`,r=x("div",{class:"mermaid"}),o=e.textContent,{svg:n,fn:i}=yield mermaid.render(t,o),s=r.attachShadow({mode:"closed"});s.innerHTML=n,e.replaceWith(r),i==null||i(s)})),so.pipe(m(()=>({ref:e})))}var ei=x("table");function ti(e){return e.replaceWith(ei),ei.replaceWith(Un(e)),$({ref:e})}function ss(e){let t=e.find(r=>r.checked)||e[0];return L(...e.map(r=>h(r,"change").pipe(m(()=>j(`label[for="${r.id}"]`))))).pipe(Q(j(`label[for="${t.id}"]`)),m(r=>({active:r})))}function ri(e,{viewport$:t,target$:r}){let o=j(".tabbed-labels",e),n=M(":scope > input",e),i=no("prev");e.append(i);let s=no("next");return e.append(s),H(()=>{let a=new T,c=a.pipe(oe(),ae(!0));z([a,Le(e),mt(e)]).pipe(W(c),$e(1,ye)).subscribe({next([{active:p},l]){let f=Be(p),{width:u}=de(p);e.style.setProperty("--md-indicator-x",`${f.x}px`),e.style.setProperty("--md-indicator-width",`${u}px`);let d=gr(o);(f.xd.x+l.width)&&o.scrollTo({left:Math.max(0,f.x-16),behavior:"smooth"})},complete(){e.style.removeProperty("--md-indicator-x"),e.style.removeProperty("--md-indicator-width")}}),z([Ge(o),Le(o)]).pipe(W(c)).subscribe(([p,l])=>{let f=At(o);i.hidden=p.x<16,s.hidden=p.x>f.width-l.width-16}),L(h(i,"click").pipe(m(()=>-1)),h(s,"click").pipe(m(()=>1))).pipe(W(c)).subscribe(p=>{let{width:l}=de(o);o.scrollBy({left:l*p,behavior:"smooth"})}),r.pipe(W(c),g(p=>n.includes(p))).subscribe(p=>p.click()),o.classList.add("tabbed-labels--linked");for(let p of n){let l=j(`label[for="${p.id}"]`);l.replaceChildren(x("a",{href:`#${l.htmlFor}`,tabIndex:-1},...Array.from(l.childNodes))),h(l.firstElementChild,"click").pipe(W(c),g(f=>!(f.metaKey||f.ctrlKey)),O(f=>{f.preventDefault(),f.stopPropagation()})).subscribe(()=>{history.replaceState({},"",`#${l.htmlFor}`),l.click()})}return V("content.tabs.link")&&a.pipe(Ie(1),te(t)).subscribe(([{active:p},{offset:l}])=>{let f=p.innerText.trim();if(p.hasAttribute("data-md-switching"))p.removeAttribute("data-md-switching");else{let u=e.offsetTop-l.y;for(let v of M("[data-tabs]"))for(let S of M(":scope > input",v)){let X=j(`label[for="${S.id}"]`);if(X!==p&&X.innerText.trim()===f){X.setAttribute("data-md-switching",""),S.click();break}}window.scrollTo({top:e.offsetTop-u});let d=__md_get("__tabs")||[];__md_set("__tabs",[...new Set([f,...d])])}}),a.pipe(W(c)).subscribe(()=>{for(let p of M("audio, video",e))p.offsetWidth&&p.autoplay?p.play().catch(()=>{}):p.pause()}),ss(n).pipe(O(p=>a.next(p)),A(()=>a.complete()),m(p=>P({ref:e},p)))}).pipe(et(pe))}function oi(e,t){let{viewport$:r,target$:o,print$:n}=t;return L(...M(".annotate:not(.highlight)",e).map(i=>zn(i,{target$:o,print$:n})),...M("pre:not(.mermaid) > code",e).map(i=>Yn(i,{target$:o,print$:n})),...M("a",e).map(i=>Jn(i,t)),...M("pre.mermaid",e).map(i=>Zn(i)),...M("table:not([class])",e).map(i=>ti(i)),...M("details",e).map(i=>Bn(i,{target$:o,print$:n})),...M("[data-tabs]",e).map(i=>ri(i,{viewport$:r,target$:o})),...M("[title]:not([data-preview])",e).filter(()=>V("content.tooltips")).map(i=>Xe(i,{viewport$:r})),...M(".footnote-ref",e).filter(()=>V("content.footnote.tooltips")).map(i=>Vt(i,{content$:new F(s=>{let a=new URL(i.href).hash.slice(1),c=Array.from(document.getElementById(a).cloneNode(!0).children),p=wr(...c);return s.next(p),document.body.append(p),()=>p.remove()}),viewport$:r})))}function cs(e,{alert$:t}){return t.pipe(b(r=>L($(!0),$(!1).pipe(nt(2e3))).pipe(m(o=>({message:r,active:o})))))}function ni(e,t){let r=j(".md-typeset",e);return H(()=>{let o=new T;return o.subscribe(({message:n,active:i})=>{e.classList.toggle("md-dialog--active",i),r.textContent=n}),cs(e,t).pipe(O(n=>o.next(n)),A(()=>o.complete()),m(n=>P({ref:e},n)))})}var ps=0;function ls(e,t){document.body.append(e);let{width:r}=de(e);e.style.setProperty("--md-tooltip-width",`${r}px`),e.remove();let o=vr(t),n=typeof o!="undefined"?Ge(o):$({x:0,y:0}),i=L(Ye(t),it(t)).pipe(Y());return z([i,n]).pipe(m(([s,a])=>{let{x:c,y:p}=Be(t),l=de(t),f=t.closest("table");return f&&t.parentElement&&(c+=f.offsetLeft+t.parentElement.offsetLeft,p+=f.offsetTop+t.parentElement.offsetTop),{active:s,offset:{x:c-a.x+l.width/2-r/2,y:p-a.y+l.height+8}}}))}function ii(e){let t=e.title;if(!t.length)return y;let r=`__tooltip_${ps++}`,o=Dt(r,"inline"),n=j(".md-typeset",o);return n.innerHTML=t,H(()=>{let i=new T;return i.subscribe({next({offset:s}){o.style.setProperty("--md-tooltip-x",`${s.x}px`),o.style.setProperty("--md-tooltip-y",`${s.y}px`)},complete(){o.style.removeProperty("--md-tooltip-x"),o.style.removeProperty("--md-tooltip-y")}}),L(i.pipe(g(({active:s})=>s)),i.pipe(Ae(250),g(({active:s})=>!s))).subscribe({next({active:s}){s?(e.insertAdjacentElement("afterend",o),e.setAttribute("aria-describedby",r),e.removeAttribute("title")):(o.remove(),e.removeAttribute("aria-describedby"),e.setAttribute("title",t))},complete(){o.remove(),e.removeAttribute("aria-describedby"),e.setAttribute("title",t)}}),i.pipe($e(16,ye)).subscribe(({active:s})=>{o.classList.toggle("md-tooltip--active",s)}),i.pipe(gt(125,ye),g(()=>!!e.offsetParent),m(()=>e.offsetParent.getBoundingClientRect()),m(({x:s})=>s)).subscribe({next(s){s?o.style.setProperty("--md-tooltip-0",`${-s}px`):o.style.removeProperty("--md-tooltip-0")},complete(){o.style.removeProperty("--md-tooltip-0")}}),ls(o,e).pipe(O(s=>i.next(s)),A(()=>i.complete()),m(s=>P({ref:e},s)))}).pipe(et(pe))}function ms({viewport$:e}){if(!V("header.autohide"))return $(!1);let t=e.pipe(m(({offset:{y:n}})=>n),ot(2,1),m(([n,i])=>[nMath.abs(i-n.y)>100),m(([,[n]])=>n),Y()),o=Je("search");return z([e,o]).pipe(m(([{offset:n},i])=>n.y>400&&!i),Y(),b(n=>n?r:$(!1)),Q(!1))}function ai(e,t){return H(()=>z([Le(e),ms(t)])).pipe(m(([{height:r},o])=>({height:r,hidden:o})),Y((r,o)=>r.height===o.height&&r.hidden===o.hidden),Z(1))}function si(e,{header$:t,main$:r}){return H(()=>{let o=new T,n=o.pipe(oe(),ae(!0));o.pipe(ne("active"),Pe(t)).subscribe(([{active:s},{hidden:a}])=>{e.classList.toggle("md-header--shadow",s&&!a),e.hidden=a});let i=fe(M("[title]",e)).pipe(g(()=>V("content.tooltips")),J(s=>ii(s)));return r.subscribe(o),t.pipe(W(n),m(s=>P({ref:e},s)),Ve(i.pipe(W(n))))})}function fs(e,{viewport$:t,header$:r}){return Er(e,{viewport$:t,header$:r}).pipe(m(({offset:{y:o}})=>{let{height:n}=de(e);return{active:n>0&&o>=n}}),ne("active"))}function ci(e,t){return H(()=>{let r=new T;r.subscribe({next({active:n}){e.classList.toggle("md-header__title--active",n)},complete(){e.classList.remove("md-header__title--active")}});let o=ue(".md-content h1");return typeof o=="undefined"?y:fs(o,t).pipe(O(n=>r.next(n)),A(()=>r.complete()),m(n=>P({ref:e},n)))})}function pi(e,{viewport$:t,header$:r}){let o=r.pipe(m(({height:i})=>i),Y()),n=o.pipe(b(()=>Le(e).pipe(m(({height:i})=>({top:e.offsetTop,bottom:e.offsetTop+i})),ne("bottom"))));return z([o,n,t]).pipe(m(([i,{top:s,bottom:a},{offset:{y:c},size:{height:p}}])=>(p=Math.max(0,p-Math.max(0,s-c,i)-Math.max(0,p+c-a)),{offset:s-i,height:p,active:s-i<=c})),Y((i,s)=>i.offset===s.offset&&i.height===s.height&&i.active===s.active))}function us(e){let t=__md_get("__palette")||{index:e.findIndex(o=>matchMedia(o.getAttribute("data-md-color-media")).matches)},r=Math.max(0,Math.min(t.index,e.length-1));return $(...e).pipe(J(o=>h(o,"change").pipe(m(()=>o))),Q(e[r]),m(o=>({index:e.indexOf(o),color:{media:o.getAttribute("data-md-color-media"),scheme:o.getAttribute("data-md-color-scheme"),primary:o.getAttribute("data-md-color-primary"),accent:o.getAttribute("data-md-color-accent")}})),Z(1))}function li(e){let t=M("input",e),r=x("meta",{name:"theme-color"});document.head.appendChild(r);let o=x("meta",{name:"color-scheme"});document.head.appendChild(o);let n=Wt("(prefers-color-scheme: light)");return H(()=>{let i=new T;return i.subscribe(s=>{if(document.body.setAttribute("data-md-color-switching",""),s.color.media==="(prefers-color-scheme)"){let a=matchMedia("(prefers-color-scheme: light)"),c=document.querySelector(a.matches?"[data-md-color-media='(prefers-color-scheme: light)']":"[data-md-color-media='(prefers-color-scheme: dark)']");s.color.scheme=c.getAttribute("data-md-color-scheme"),s.color.primary=c.getAttribute("data-md-color-primary"),s.color.accent=c.getAttribute("data-md-color-accent")}for(let[a,c]of Object.entries(s.color))document.body.setAttribute(`data-md-color-${a}`,c);for(let a=0;as.key==="Enter"),te(i,(s,a)=>a)).subscribe(({index:s})=>{s=(s+1)%t.length,t[s].click(),t[s].focus()}),i.pipe(m(()=>{let s=Ce("header"),a=window.getComputedStyle(s);return o.content=a.colorScheme,a.backgroundColor.match(/\d+/g).map(c=>(+c).toString(16).padStart(2,"0")).join("")})).subscribe(s=>r.content=`#${s}`),i.pipe(xe(pe)).subscribe(()=>{document.body.removeAttribute("data-md-color-switching")}),us(t).pipe(W(n.pipe(Ie(1))),vt(),O(s=>i.next(s)),A(()=>i.complete()),m(s=>P({ref:e},s)))})}function mi(e,{progress$:t}){return H(()=>{let r=new T;return r.subscribe(({value:o})=>{e.style.setProperty("--md-progress-value",`${o}`)}),t.pipe(O(o=>r.next({value:o})),A(()=>r.complete()),m(o=>({ref:e,value:o})))})}function fi(e,t){return e.protocol=t.protocol,e.hostname=t.hostname,e}function ds(e,t){let r=new Map;for(let o of M("url",e)){let n=j("loc",o),i=[fi(new URL(n.textContent),t)];r.set(`${i[0]}`,i);for(let s of M("[rel=alternate]",o)){let a=s.getAttribute("href");a!=null&&i.push(fi(new URL(a),t))}}return r}function kt(e){return En(new URL("sitemap.xml",e)).pipe(m(t=>ds(t,new URL(e))),ve(()=>$(new Map)),le())}function ui({document$:e}){let t=new Map;e.pipe(b(()=>M("link[rel=alternate]")),m(r=>new URL(r.href)),g(r=>!t.has(r.toString())),J(r=>kt(r).pipe(m(o=>[r,o]),ve(()=>y)))).subscribe(([r,o])=>{t.set(r.toString().replace(/\/$/,""),o)}),h(document.body,"click").pipe(g(r=>!r.metaKey&&!r.ctrlKey),b(r=>{if(r.target instanceof Element){let o=r.target.closest("a");if(o&&!o.target){let n=[...t].find(([f])=>o.href.startsWith(`${f}/`));if(typeof n=="undefined")return y;let[i,s]=n,a=we();if(a.href.startsWith(i))return y;let c=Te(),p=a.href.replace(c.base,"");p=`${i}/${p}`;let l=s.has(p.split("#")[0])?new URL(p,c.base):new URL(i);return r.preventDefault(),$(l)}}return y})).subscribe(r=>st(r,!0))}var co=$t(ao());function hs(e){e.setAttribute("data-md-copying","");let t=e.closest("[data-copy]"),r=t?t.getAttribute("data-copy"):e.innerText;return e.removeAttribute("data-md-copying"),r.trimEnd()}function di({alert$:e}){co.default.isSupported()&&new F(t=>{new co.default("[data-clipboard-target], [data-clipboard-text]",{text:r=>r.getAttribute("data-clipboard-text")||hs(j(r.getAttribute("data-clipboard-target")))}).on("success",r=>t.next(r))}).pipe(O(t=>{t.trigger.focus()}),m(()=>Me("clipboard.copied"))).subscribe(e)}function hi(e,t){if(!(e.target instanceof Element))return y;let r=e.target.closest("a");if(r===null)return y;if(r.target||e.metaKey||e.ctrlKey)return y;let o=new URL(r.href);return o.search=o.hash="",t.has(`${o}`)?(e.preventDefault(),$(r)):y}function bi(e){let t=new Map;for(let r of M(":scope > *",e.head))t.set(r.outerHTML,r);return t}function vi(e){for(let t of M("[href], [src]",e))for(let r of["href","src"]){let o=t.getAttribute(r);if(o&&!/^(?:[a-z]+:)?\/\//i.test(o)){t[r]=t[r];break}}return $(e)}function bs(e){for(let o of["[data-md-component=announce]","[data-md-component=container]","[data-md-component=header-topic]","[data-md-component=outdated]","[data-md-component=logo]","[data-md-component=skip]",...V("navigation.tabs.sticky")?["[data-md-component=tabs]"]:[]]){let n=ue(o),i=ue(o,e);typeof n!="undefined"&&typeof i!="undefined"&&n.replaceWith(i)}let t=bi(document);for(let[o,n]of bi(e))t.has(o)?t.delete(o):document.head.appendChild(n);for(let o of t.values()){let n=o.getAttribute("name");n!=="theme-color"&&n!=="color-scheme"&&o.remove()}let r=Ce("container");return Ke(M("script",r)).pipe(b(o=>{let n=e.createElement("script");if(o.src){for(let i of o.getAttributeNames())n.setAttribute(i,o.getAttribute(i));return o.replaceWith(n),new F(i=>{n.onload=()=>i.complete()})}else return n.textContent=o.textContent,o.replaceWith(n),y}),oe(),ae(document))}function gi({sitemap$:e,location$:t,viewport$:r,progress$:o}){if(location.protocol==="file:")return y;$(document).subscribe(vi);let n=h(document.body,"click").pipe(Pe(e),b(([a,c])=>hi(a,c)),m(({href:a})=>new URL(a)),le()),i=h(window,"popstate").pipe(m(we),le());n.pipe(te(r)).subscribe(([a,{offset:c}])=>{history.replaceState(c,""),history.pushState(null,"",a)}),L(n,i).subscribe(t);let s=t.pipe(ne("pathname"),b(a=>xr(a,{progress$:o}).pipe(ve(()=>(st(a,!0),y)))),b(vi),b(bs),le());return L(s.pipe(te(t,(a,c)=>c)),s.pipe(b(()=>t),ne("hash")),t.pipe(Y((a,c)=>a.pathname===c.pathname&&a.hash===c.hash),b(()=>n),O(()=>history.back()))).subscribe(a=>{var c,p;history.state!==null||!a.hash?window.scrollTo(0,(p=(c=history.state)==null?void 0:c.y)!=null?p:0):(history.scrollRestoration="auto",gn(a.hash),history.scrollRestoration="manual")}),t.subscribe(()=>{history.scrollRestoration="manual"}),h(window,"beforeunload").subscribe(()=>{history.scrollRestoration="auto"}),r.pipe(ne("offset"),Ae(100)).subscribe(({offset:a})=>{history.replaceState(a,"")}),V("navigation.instant.prefetch")&&L(h(document.body,"mousemove"),h(document.body,"focusin")).pipe(Pe(e),b(([a,c])=>hi(a,c)),Ae(25),Qr(({href:a})=>a),hr(a=>{let c=document.createElement("link");return c.rel="prefetch",c.href=a.toString(),document.head.appendChild(c),h(c,"load").pipe(m(()=>c),Ee(1))})).subscribe(a=>a.remove()),s}var yi=$t(ro());function xi(e){let t=e.separator.split("|").map(n=>n.replace(/(\(\?[!=<][^)]+\))/g,"").length===0?"\uFFFD":n).join("|"),r=new RegExp(t,"img"),o=(n,i,s)=>`${i}${s}`;return n=>{n=n.replace(/[\s*+\-:~^]+/g," ").replace(/&/g,"&").trim();let i=new RegExp(`(^|${e.separator}|)(${n.replace(/[|\\{}()[\]^$+*?.-]/g,"\\$&").replace(r,"|")})`,"img");return s=>(0,yi.default)(s).replace(i,o).replace(/<\/mark>(\s+)]*>/img,"$1")}}function zt(e){return e.type===1}function Sr(e){return e.type===3}function Ei(e,t){let r=Mn(e);return L($(location.protocol!=="file:"),Je("search")).pipe(Re(o=>o),b(()=>t)).subscribe(({config:o,docs:n})=>r.next({type:0,data:{config:o,docs:n,options:{suggest:V("search.suggest")}}})),r}function wi(e){var l;let{selectedVersionSitemap:t,selectedVersionBaseURL:r,currentLocation:o,currentBaseURL:n}=e,i=(l=po(n))==null?void 0:l.pathname;if(i===void 0)return;let s=ys(o.pathname,i);if(s===void 0)return;let a=Es(t.keys());if(!t.has(a))return;let c=po(s,a);if(!c||!t.has(c.href))return;let p=po(s,r);if(p)return p.hash=o.hash,p.search=o.search,p}function po(e,t){try{return new URL(e,t)}catch(r){return}}function ys(e,t){if(e.startsWith(t))return e.slice(t.length)}function xs(e,t){let r=Math.min(e.length,t.length),o;for(o=0;oy)),o=r.pipe(m(n=>{let[,i]=t.base.match(/([^/]+)\/?$/);return n.find(({version:s,aliases:a})=>s===i||a.includes(i))||n[0]}));r.pipe(m(n=>new Map(n.map(i=>[`${new URL(`../${i.version}/`,t.base)}`,i]))),b(n=>h(document.body,"click").pipe(g(i=>!i.metaKey&&!i.ctrlKey),te(o),b(([i,s])=>{if(i.target instanceof Element){let a=i.target.closest("a");if(a&&!a.target&&n.has(a.href)){let c=a.href;return!i.target.closest(".md-version")&&n.get(c)===s?y:(i.preventDefault(),$(new URL(c)))}}return y}),b(i=>kt(i).pipe(m(s=>{var a;return(a=wi({selectedVersionSitemap:s,selectedVersionBaseURL:i,currentLocation:we(),currentBaseURL:t.base}))!=null?a:i})))))).subscribe(n=>st(n,!0)),z([r,o]).subscribe(([n,i])=>{j(".md-header__topic").appendChild(Wn(n,i))}),e.pipe(b(()=>o)).subscribe(n=>{var a;let i=new URL(t.base),s=__md_get("__outdated",sessionStorage,i);if(s===null){s=!0;let c=((a=t.version)==null?void 0:a.default)||"latest";Array.isArray(c)||(c=[c]);e:for(let p of c)for(let l of n.aliases.concat(n.version))if(new RegExp(p,"i").test(l)){s=!1;break e}__md_set("__outdated",s,sessionStorage,i)}if(s)for(let c of me("outdated"))c.hidden=!1})}function ws(e,{worker$:t}){let{searchParams:r}=we();r.has("q")&&(at("search",!0),e.value=r.get("q"),e.focus(),Je("search").pipe(Re(i=>!i)).subscribe(()=>{let i=we();i.searchParams.delete("q"),history.replaceState({},"",`${i}`)}));let o=Ye(e),n=L(t.pipe(Re(zt)),h(e,"keyup"),o).pipe(m(()=>e.value),Y());return z([n,o]).pipe(m(([i,s])=>({value:i,focus:s})),Z(1))}function Si(e,{worker$:t}){let r=new T,o=r.pipe(oe(),ae(!0));z([t.pipe(Re(zt)),r],(i,s)=>s).pipe(ne("value")).subscribe(({value:i})=>t.next({type:2,data:i})),r.pipe(ne("focus")).subscribe(({focus:i})=>{i&&at("search",i)}),h(e.form,"reset").pipe(W(o)).subscribe(()=>e.focus());let n=j("header [for=__search]");return h(n,"click").subscribe(()=>e.focus()),ws(e,{worker$:t}).pipe(O(i=>r.next(i)),A(()=>r.complete()),m(i=>P({ref:e},i)),Z(1))}function Oi(e,{worker$:t,query$:r}){let o=new T,n=un(e.parentElement).pipe(g(Boolean)),i=e.parentElement,s=j(":scope > :first-child",e),a=j(":scope > :last-child",e);Je("search").subscribe(l=>{a.setAttribute("role",l?"list":"presentation"),a.hidden=!l}),o.pipe(te(r),Gr(t.pipe(Re(zt)))).subscribe(([{items:l},{value:f}])=>{switch(l.length){case 0:s.textContent=f.length?Me("search.result.none"):Me("search.result.placeholder");break;case 1:s.textContent=Me("search.result.one");break;default:let u=br(l.length);s.textContent=Me("search.result.other",u)}});let c=o.pipe(O(()=>a.innerHTML=""),b(({items:l})=>L($(...l.slice(0,10)),$(...l.slice(10)).pipe(ot(4),Xr(n),b(([f])=>f)))),m(Fn),le());return c.subscribe(l=>a.appendChild(l)),c.pipe(J(l=>{let f=ue("details",l);return typeof f=="undefined"?y:h(f,"toggle").pipe(W(o),m(()=>f))})).subscribe(l=>{l.open===!1&&l.offsetTop<=i.scrollTop&&i.scrollTo({top:l.offsetTop})}),t.pipe(g(Sr),m(({data:l})=>l)).pipe(O(l=>o.next(l)),A(()=>o.complete()),m(l=>P({ref:e},l)))}function Ts(e,{query$:t}){return t.pipe(m(({value:r})=>{let o=we();return o.hash="",r=r.replace(/\s+/g,"+").replace(/&/g,"%26").replace(/=/g,"%3D"),o.search=`q=${r}`,{url:o}}))}function Li(e,t){let r=new T,o=r.pipe(oe(),ae(!0));return r.subscribe(({url:n})=>{e.setAttribute("data-clipboard-text",e.href),e.href=`${n}`}),h(e,"click").pipe(W(o)).subscribe(n=>n.preventDefault()),Ts(e,t).pipe(O(n=>r.next(n)),A(()=>r.complete()),m(n=>P({ref:e},n)))}function Mi(e,{worker$:t,keyboard$:r}){let o=new T,n=Ce("search-query"),i=L(h(n,"keydown"),h(n,"focus")).pipe(xe(pe),m(()=>n.value),Y());return o.pipe(Pe(i),m(([{suggest:a},c])=>{let p=c.split(/([\s-]+)/);if(a!=null&&a.length&&p[p.length-1]){let l=a[a.length-1];l.startsWith(p[p.length-1])&&(p[p.length-1]=l)}else p.length=0;return p})).subscribe(a=>e.innerHTML=a.join("").replace(/\s/g," ")),r.pipe(g(({mode:a})=>a==="search")).subscribe(a=>{a.type==="ArrowRight"&&e.innerText.length&&n.selectionStart===n.value.length&&(n.value=e.innerText)}),t.pipe(g(Sr),m(({data:a})=>a)).pipe(O(a=>o.next(a)),A(()=>o.complete()),m(()=>({ref:e})))}function _i(e,{index$:t,keyboard$:r}){let o=Te();try{let n=Ei(o.search,t),i=Ce("search-query",e),s=Ce("search-result",e);h(e,"click").pipe(g(({target:c})=>c instanceof Element&&!!c.closest("a"))).subscribe(()=>at("search",!1)),r.pipe(g(({mode:c})=>c==="search")).subscribe(c=>{let p=Ne();switch(c.type){case"Enter":if(p===i){let l=new Map;for(let f of M(":first-child [href]",s)){let u=f.firstElementChild;l.set(f,parseFloat(u.getAttribute("data-md-score")))}if(l.size){let[[f]]=[...l].sort(([,u],[,d])=>d-u);f.click()}c.claim()}break;case"Escape":case"Tab":at("search",!1),i.blur();break;case"ArrowUp":case"ArrowDown":if(typeof p=="undefined")i.focus();else{let l=[i,...M(":not(details) > [href], summary, details[open] [href]",s)],f=Math.max(0,(Math.max(0,l.indexOf(p))+l.length+(c.type==="ArrowUp"?-1:1))%l.length);l[f].focus()}c.claim();break;default:i!==Ne()&&i.focus()}}),r.pipe(g(({mode:c})=>c==="global")).subscribe(c=>{switch(c.type){case"f":case"s":case"/":i.focus(),i.select(),c.claim();break}});let a=Si(i,{worker$:n});return L(a,Oi(s,{worker$:n,query$:a})).pipe(Ve(...me("search-share",e).map(c=>Li(c,{query$:a})),...me("search-suggest",e).map(c=>Mi(c,{worker$:n,keyboard$:r}))))}catch(n){return e.hidden=!0,tt}}function Ai(e,{index$:t,location$:r}){return z([t,r.pipe(Q(we()),g(o=>!!o.searchParams.get("h")))]).pipe(m(([o,n])=>xi(o.config)(n.searchParams.get("h"))),m(o=>{var s;let n=new Map,i=document.createNodeIterator(e,NodeFilter.SHOW_TEXT);for(let a=i.nextNode();a;a=i.nextNode())if((s=a.parentElement)!=null&&s.offsetHeight){let c=a.textContent,p=o(c);p.length>c.length&&n.set(a,p)}for(let[a,c]of n){let{childNodes:p}=x("span",null,c);a.replaceWith(...Array.from(p))}return{ref:e,nodes:n}}))}function Ss(e,{viewport$:t,main$:r}){let o=e.closest(".md-grid"),n=o.offsetTop-o.parentElement.offsetTop;return z([r,t]).pipe(m(([{offset:i,height:s},{offset:{y:a}}])=>(s=s+Math.min(n,Math.max(0,a-i))-n,{height:s,locked:a>=i+n})),Y((i,s)=>i.height===s.height&&i.locked===s.locked))}function lo(e,o){var n=o,{header$:t}=n,r=vo(n,["header$"]);let i=j(".md-sidebar__scrollwrap",e),{y:s}=Be(i);return H(()=>{let a=new T,c=a.pipe(oe(),ae(!0)),p=a.pipe($e(0,ye));return p.pipe(te(t)).subscribe({next([{height:l},{height:f}]){i.style.height=`${l-2*s}px`,e.style.top=`${f}px`},complete(){i.style.height="",e.style.top=""}}),p.pipe(Re()).subscribe(()=>{for(let l of M(".md-nav__link--active[href]",e)){if(!l.clientHeight)continue;let f=l.closest(".md-sidebar__scrollwrap");if(typeof f!="undefined"){let u=l.offsetTop-f.offsetTop,{height:d}=de(f);f.scrollTo({top:u-d/2})}}}),fe(M("label[tabindex]",e)).pipe(J(l=>h(l,"click").pipe(xe(pe),m(()=>l),W(c)))).subscribe(l=>{let f=j(`[id="${l.htmlFor}"]`);j(`[aria-labelledby="${l.id}"]`).setAttribute("aria-expanded",`${f.checked}`)}),V("content.tooltips")&&fe(M("abbr[title]",e)).pipe(J(l=>Xe(l,{viewport$})),W(c)).subscribe(),Ss(e,r).pipe(O(l=>a.next(l)),A(()=>a.complete()),m(l=>P({ref:e},l)))})}function Ci(e,t){if(typeof t!="undefined"){let r=`https://api.github.com/repos/${e}/${t}`;return rt(ze(`${r}/releases/latest`).pipe(ve(()=>y),m(o=>({version:o.tag_name})),Qe({})),ze(r).pipe(ve(()=>y),m(o=>({stars:o.stargazers_count,forks:o.forks_count})),Qe({}))).pipe(m(([o,n])=>P(P({},o),n)))}else{let r=`https://api.github.com/users/${e}`;return ze(r).pipe(m(o=>({repositories:o.public_repos})),Qe({}))}}function ki(e,t){let r=`https://${e}/api/v4/projects/${encodeURIComponent(t)}`;return rt(ze(`${r}/releases/permalink/latest`).pipe(ve(()=>y),m(({tag_name:o})=>({version:o})),Qe({})),ze(r).pipe(ve(()=>y),m(({star_count:o,forks_count:n})=>({stars:o,forks:n})),Qe({}))).pipe(m(([o,n])=>P(P({},o),n)))}function Hi(e){let t=e.match(/^.+github\.com\/([^/]+)\/?([^/]+)?/i);if(t){let[,r,o]=t;return Ci(r,o)}if(t=e.match(/^.+?([^/]*gitlab[^/]+)\/(.+?)\/?$/i),t){let[,r,o]=t;return ki(r,o)}return y}var Os;function Ls(e){return Os||(Os=H(()=>{let t=__md_get("__source",sessionStorage);if(t)return $(t);if(me("consent").length){let o=__md_get("__consent");if(!(o&&o.github))return y}return Hi(e.href).pipe(O(o=>__md_set("__source",o,sessionStorage)))}).pipe(ve(()=>y),g(t=>Object.keys(t).length>0),m(t=>({facts:t})),Z(1)))}function $i(e){let t=j(":scope > :last-child",e);return H(()=>{let r=new T;return r.subscribe(({facts:o})=>{t.appendChild(jn(o)),t.classList.add("md-source__repository--active")}),Ls(e).pipe(O(o=>r.next(o)),A(()=>r.complete()),m(o=>P({ref:e},o)))})}function Ms(e,{viewport$:t,header$:r}){return Le(document.body).pipe(b(()=>Er(e,{header$:r,viewport$:t})),m(({offset:{y:o}})=>({hidden:o>=10})),ne("hidden"))}function Pi(e,t){return H(()=>{let r=new T;return r.subscribe({next({hidden:o}){e.hidden=o},complete(){e.hidden=!1}}),(V("navigation.tabs.sticky")?$({hidden:!1}):Ms(e,t)).pipe(O(o=>r.next(o)),A(()=>r.complete()),m(o=>P({ref:e},o)))})}function _s(e,{viewport$:t,header$:r}){let o=new Map,n=M(".md-nav__link",e);for(let a of n){let c=decodeURIComponent(a.hash.substring(1)),p=ue(`[id="${c}"]`);typeof p!="undefined"&&o.set(a,p)}let i=r.pipe(ne("height"),m(({height:a})=>{let c=Ce("main"),p=j(":scope > :first-child",c);return a+.8*(p.offsetTop-c.offsetTop)}),le());return Le(document.body).pipe(ne("height"),b(a=>H(()=>{let c=[];return $([...o].reduce((p,[l,f])=>{for(;c.length&&o.get(c[c.length-1]).tagName>=f.tagName;)c.pop();let u=f.offsetTop;for(;!u&&f.parentElement;)f=f.parentElement,u=f.offsetTop;let d=f.offsetParent;for(;d;d=d.offsetParent)u+=d.offsetTop;return p.set([...c=[...c,l]].reverse(),u)},new Map))}).pipe(m(c=>new Map([...c].sort(([,p],[,l])=>p-l))),Pe(i),b(([c,p])=>t.pipe(Ut(([l,f],{offset:{y:u},size:d})=>{let v=u+d.height>=Math.floor(a.height);for(;f.length;){let[,S]=f[0];if(S-p=u&&!v)f=[l.pop(),...f];else break}return[l,f]},[[],[...c]]),Y((l,f)=>l[0]===f[0]&&l[1]===f[1])))))).pipe(m(([a,c])=>({prev:a.map(([p])=>p),next:c.map(([p])=>p)})),Q({prev:[],next:[]}),ot(2,1),m(([a,c])=>a.prev.length{let i=new T,s=i.pipe(oe(),ae(!0));if(i.subscribe(({prev:a,next:c})=>{for(let[p]of c)p.classList.remove("md-nav__link--passed"),p.classList.remove("md-nav__link--active");for(let[p,[l]]of a.entries())l.classList.add("md-nav__link--passed"),l.classList.toggle("md-nav__link--active",p===a.length-1)}),V("toc.follow")){let a=L(t.pipe(Ae(1),m(()=>{})),t.pipe(Ae(250),m(()=>"smooth")));i.pipe(g(({prev:c})=>c.length>0),Pe(o.pipe(xe(pe))),te(a)).subscribe(([[{prev:c}],p])=>{let[l]=c[c.length-1];if(l.offsetHeight){let f=vr(l);if(typeof f!="undefined"){let u=l.offsetTop-f.offsetTop,{height:d}=de(f);f.scrollTo({top:u-d/2,behavior:p})}}})}return V("navigation.tracking")&&t.pipe(W(s),ne("offset"),Ae(250),Ie(1),W(n.pipe(Ie(1))),vt({delay:250}),te(i)).subscribe(([,{prev:a}])=>{let c=we(),p=a[a.length-1];if(p&&p.length){let[l]=p,{hash:f}=new URL(l.href);c.hash!==f&&(c.hash=f,history.replaceState({},"",`${c}`))}else c.hash="",history.replaceState({},"",`${c}`)}),_s(e,{viewport$:t,header$:r}).pipe(O(a=>i.next(a)),A(()=>i.complete()),m(a=>P({ref:e},a)))})}function As(e,{viewport$:t,main$:r,target$:o}){let n=t.pipe(m(({offset:{y:s}})=>s),ot(2,1),m(([s,a])=>s>a&&a>0),Y()),i=r.pipe(m(({active:s})=>s));return z([i,n]).pipe(m(([s,a])=>!(s&&a)),Y(),W(o.pipe(Ie(1))),ae(!0),vt({delay:250}),m(s=>({hidden:s})))}function Ii(e,{viewport$:t,header$:r,main$:o,target$:n}){let i=new T,s=i.pipe(oe(),ae(!0));return i.subscribe({next({hidden:a}){e.hidden=a,a?(e.setAttribute("tabindex","-1"),e.blur()):e.removeAttribute("tabindex")},complete(){e.style.top="",e.hidden=!0,e.removeAttribute("tabindex")}}),r.pipe(W(s),ne("height")).subscribe(({height:a})=>{e.style.top=`${a+16}px`}),h(e,"click").subscribe(a=>{a.preventDefault(),window.scrollTo({top:0})}),As(e,{viewport$:t,main$:o,target$:n}).pipe(O(a=>i.next(a)),A(()=>i.complete()),m(a=>P({ref:e},a)))}function Fi({document$:e,viewport$:t}){e.pipe(b(()=>M(".md-ellipsis")),J(r=>mt(r).pipe(W(e.pipe(Ie(1))),g(o=>o),m(()=>r),Ee(1))),g(r=>r.offsetWidth{let o=r.innerText,n=r.closest("a")||r;return n.title=o,V("content.tooltips")?Xe(n,{viewport$:t}).pipe(W(e.pipe(Ie(1))),A(()=>n.removeAttribute("title"))):y})).subscribe(),V("content.tooltips")&&e.pipe(b(()=>M(".md-status")),J(r=>Xe(r,{viewport$:t}))).subscribe()}function ji({document$:e,tablet$:t}){e.pipe(b(()=>M(".md-toggle--indeterminate")),O(r=>{r.indeterminate=!0,r.checked=!1}),J(r=>h(r,"change").pipe(Jr(()=>r.classList.contains("md-toggle--indeterminate")),m(()=>r))),te(t)).subscribe(([r,o])=>{r.classList.remove("md-toggle--indeterminate"),o&&(r.checked=!1)})}function Cs(){return/(iPad|iPhone|iPod)/.test(navigator.userAgent)}function Ui({document$:e}){e.pipe(b(()=>M("[data-md-scrollfix]")),O(t=>t.removeAttribute("data-md-scrollfix")),g(Cs),J(t=>h(t,"touchstart").pipe(m(()=>t)))).subscribe(t=>{let r=t.scrollTop;r===0?t.scrollTop=1:r+t.offsetHeight===t.scrollHeight&&(t.scrollTop=r-1)})}function Wi({viewport$:e,tablet$:t}){z([Je("search"),t]).pipe(m(([r,o])=>r&&!o),b(r=>$(r).pipe(nt(r?400:100))),te(e)).subscribe(([r,{offset:{y:o}}])=>{if(r)document.body.setAttribute("data-md-scrolllock",""),document.body.style.top=`-${o}px`;else{let n=-1*parseInt(document.body.style.top,10);document.body.removeAttribute("data-md-scrolllock"),document.body.style.top="",n&&window.scrollTo(0,n)}})}Object.entries||(Object.entries=function(e){let t=[];for(let r of Object.keys(e))t.push([r,e[r]]);return t});Object.values||(Object.values=function(e){let t=[];for(let r of Object.keys(e))t.push(e[r]);return t});typeof Element!="undefined"&&(Element.prototype.scrollTo||(Element.prototype.scrollTo=function(e,t){typeof e=="object"?(this.scrollLeft=e.left,this.scrollTop=e.top):(this.scrollLeft=e,this.scrollTop=t)}),Element.prototype.replaceWith||(Element.prototype.replaceWith=function(...e){let t=this.parentNode;if(t){e.length===0&&t.removeChild(this);for(let r=e.length-1;r>=0;r--){let o=e[r];typeof o=="string"?o=document.createTextNode(o):o.parentNode&&o.parentNode.removeChild(o),r?t.insertBefore(this.previousSibling,o):t.replaceChild(o,this)}}}));function ks(){return location.protocol==="file:"?_t(`${new URL("search/search_index.js",Or.base)}`).pipe(m(()=>__index),Z(1)):ze(new URL("search/search_index.json",Or.base))}document.documentElement.classList.remove("no-js");document.documentElement.classList.add("js");var ct=an(),Kt=bn(),Ht=yn(Kt),mo=hn(),ke=Ln(),Lr=Wt("(min-width: 60em)"),Vi=Wt("(min-width: 76.25em)"),Ni=xn(),Or=Te(),zi=document.forms.namedItem("search")?ks():tt,fo=new T;di({alert$:fo});ui({document$:ct});var uo=new T,qi=kt(Or.base);V("navigation.instant")&&gi({sitemap$:qi,location$:Kt,viewport$:ke,progress$:uo}).subscribe(ct);var Di;((Di=Or.version)==null?void 0:Di.provider)==="mike"&&Ti({document$:ct});L(Kt,Ht).pipe(nt(125)).subscribe(()=>{at("drawer",!1),at("search",!1)});mo.pipe(g(({mode:e})=>e==="global")).subscribe(e=>{switch(e.type){case"p":case",":let t=ue("link[rel=prev]");typeof t!="undefined"&&st(t);break;case"n":case".":let r=ue("link[rel=next]");typeof r!="undefined"&&st(r);break;case"Enter":let o=Ne();o instanceof HTMLLabelElement&&o.click()}});Fi({viewport$:ke,document$:ct});ji({document$:ct,tablet$:Lr});Ui({document$:ct});Wi({viewport$:ke,tablet$:Lr});var ft=ai(Ce("header"),{viewport$:ke}),qt=ct.pipe(m(()=>Ce("main")),b(e=>pi(e,{viewport$:ke,header$:ft})),Z(1)),Hs=L(...me("consent").map(e=>An(e,{target$:Ht})),...me("dialog").map(e=>ni(e,{alert$:fo})),...me("palette").map(e=>li(e)),...me("progress").map(e=>mi(e,{progress$:uo})),...me("search").map(e=>_i(e,{index$:zi,keyboard$:mo})),...me("source").map(e=>$i(e))),$s=H(()=>L(...me("announce").map(e=>_n(e)),...me("content").map(e=>oi(e,{sitemap$:qi,viewport$:ke,target$:Ht,print$:Ni})),...me("content").map(e=>V("search.highlight")?Ai(e,{index$:zi,location$:Kt}):y),...me("header").map(e=>si(e,{viewport$:ke,header$:ft,main$:qt})),...me("header-title").map(e=>ci(e,{viewport$:ke,header$:ft})),...me("sidebar").map(e=>e.getAttribute("data-md-type")==="navigation"?eo(Vi,()=>lo(e,{viewport$:ke,header$:ft,main$:qt})):eo(Lr,()=>lo(e,{viewport$:ke,header$:ft,main$:qt}))),...me("tabs").map(e=>Pi(e,{viewport$:ke,header$:ft})),...me("toc").map(e=>Ri(e,{viewport$:ke,header$:ft,main$:qt,target$:Ht})),...me("top").map(e=>Ii(e,{viewport$:ke,header$:ft,main$:qt,target$:Ht})))),Ki=ct.pipe(b(()=>$s),Ve(Hs),Z(1));Ki.subscribe();window.document$=ct;window.location$=Kt;window.target$=Ht;window.keyboard$=mo;window.viewport$=ke;window.tablet$=Lr;window.screen$=Vi;window.print$=Ni;window.alert$=fo;window.progress$=uo;window.component$=Ki;})(); -//# sourceMappingURL=bundle.79ae519e.min.js.map - diff --git a/site/assets/javascripts/bundle.79ae519e.min.js.map b/site/assets/javascripts/bundle.79ae519e.min.js.map deleted file mode 100644 index 5cf0289..0000000 --- a/site/assets/javascripts/bundle.79ae519e.min.js.map +++ /dev/null @@ -1,7 +0,0 @@ -{ - "version": 3, - "sources": ["node_modules/focus-visible/dist/focus-visible.js", "node_modules/escape-html/index.js", "node_modules/clipboard/dist/clipboard.js", "src/templates/assets/javascripts/bundle.ts", "node_modules/tslib/tslib.es6.mjs", "node_modules/rxjs/src/internal/util/isFunction.ts", "node_modules/rxjs/src/internal/util/createErrorClass.ts", "node_modules/rxjs/src/internal/util/UnsubscriptionError.ts", "node_modules/rxjs/src/internal/util/arrRemove.ts", "node_modules/rxjs/src/internal/Subscription.ts", "node_modules/rxjs/src/internal/config.ts", "node_modules/rxjs/src/internal/scheduler/timeoutProvider.ts", "node_modules/rxjs/src/internal/util/reportUnhandledError.ts", "node_modules/rxjs/src/internal/util/noop.ts", "node_modules/rxjs/src/internal/NotificationFactories.ts", "node_modules/rxjs/src/internal/util/errorContext.ts", "node_modules/rxjs/src/internal/Subscriber.ts", "node_modules/rxjs/src/internal/symbol/observable.ts", "node_modules/rxjs/src/internal/util/identity.ts", "node_modules/rxjs/src/internal/util/pipe.ts", "node_modules/rxjs/src/internal/Observable.ts", "node_modules/rxjs/src/internal/util/lift.ts", "node_modules/rxjs/src/internal/operators/OperatorSubscriber.ts", "node_modules/rxjs/src/internal/scheduler/animationFrameProvider.ts", "node_modules/rxjs/src/internal/util/ObjectUnsubscribedError.ts", "node_modules/rxjs/src/internal/Subject.ts", "node_modules/rxjs/src/internal/BehaviorSubject.ts", "node_modules/rxjs/src/internal/scheduler/dateTimestampProvider.ts", "node_modules/rxjs/src/internal/ReplaySubject.ts", "node_modules/rxjs/src/internal/scheduler/Action.ts", "node_modules/rxjs/src/internal/scheduler/intervalProvider.ts", "node_modules/rxjs/src/internal/scheduler/AsyncAction.ts", "node_modules/rxjs/src/internal/Scheduler.ts", "node_modules/rxjs/src/internal/scheduler/AsyncScheduler.ts", "node_modules/rxjs/src/internal/scheduler/async.ts", "node_modules/rxjs/src/internal/scheduler/QueueAction.ts", "node_modules/rxjs/src/internal/scheduler/QueueScheduler.ts", "node_modules/rxjs/src/internal/scheduler/queue.ts", "node_modules/rxjs/src/internal/scheduler/AnimationFrameAction.ts", "node_modules/rxjs/src/internal/scheduler/AnimationFrameScheduler.ts", "node_modules/rxjs/src/internal/scheduler/animationFrame.ts", "node_modules/rxjs/src/internal/observable/empty.ts", "node_modules/rxjs/src/internal/util/isScheduler.ts", "node_modules/rxjs/src/internal/util/args.ts", "node_modules/rxjs/src/internal/util/isArrayLike.ts", "node_modules/rxjs/src/internal/util/isPromise.ts", "node_modules/rxjs/src/internal/util/isInteropObservable.ts", "node_modules/rxjs/src/internal/util/isAsyncIterable.ts", "node_modules/rxjs/src/internal/util/throwUnobservableError.ts", "node_modules/rxjs/src/internal/symbol/iterator.ts", "node_modules/rxjs/src/internal/util/isIterable.ts", "node_modules/rxjs/src/internal/util/isReadableStreamLike.ts", "node_modules/rxjs/src/internal/observable/innerFrom.ts", "node_modules/rxjs/src/internal/util/executeSchedule.ts", "node_modules/rxjs/src/internal/operators/observeOn.ts", "node_modules/rxjs/src/internal/operators/subscribeOn.ts", "node_modules/rxjs/src/internal/scheduled/scheduleObservable.ts", "node_modules/rxjs/src/internal/scheduled/schedulePromise.ts", "node_modules/rxjs/src/internal/scheduled/scheduleArray.ts", "node_modules/rxjs/src/internal/scheduled/scheduleIterable.ts", "node_modules/rxjs/src/internal/scheduled/scheduleAsyncIterable.ts", "node_modules/rxjs/src/internal/scheduled/scheduleReadableStreamLike.ts", "node_modules/rxjs/src/internal/scheduled/scheduled.ts", "node_modules/rxjs/src/internal/observable/from.ts", "node_modules/rxjs/src/internal/observable/of.ts", "node_modules/rxjs/src/internal/observable/throwError.ts", "node_modules/rxjs/src/internal/util/EmptyError.ts", "node_modules/rxjs/src/internal/util/isDate.ts", "node_modules/rxjs/src/internal/operators/map.ts", "node_modules/rxjs/src/internal/util/mapOneOrManyArgs.ts", "node_modules/rxjs/src/internal/util/argsArgArrayOrObject.ts", "node_modules/rxjs/src/internal/util/createObject.ts", "node_modules/rxjs/src/internal/observable/combineLatest.ts", "node_modules/rxjs/src/internal/operators/mergeInternals.ts", "node_modules/rxjs/src/internal/operators/mergeMap.ts", "node_modules/rxjs/src/internal/operators/mergeAll.ts", "node_modules/rxjs/src/internal/operators/concatAll.ts", "node_modules/rxjs/src/internal/observable/concat.ts", "node_modules/rxjs/src/internal/observable/defer.ts", "node_modules/rxjs/src/internal/observable/fromEvent.ts", "node_modules/rxjs/src/internal/observable/fromEventPattern.ts", "node_modules/rxjs/src/internal/observable/timer.ts", "node_modules/rxjs/src/internal/observable/merge.ts", "node_modules/rxjs/src/internal/observable/never.ts", "node_modules/rxjs/src/internal/util/argsOrArgArray.ts", "node_modules/rxjs/src/internal/operators/filter.ts", "node_modules/rxjs/src/internal/observable/zip.ts", "node_modules/rxjs/src/internal/operators/audit.ts", "node_modules/rxjs/src/internal/operators/auditTime.ts", "node_modules/rxjs/src/internal/operators/bufferCount.ts", "node_modules/rxjs/src/internal/operators/catchError.ts", "node_modules/rxjs/src/internal/operators/scanInternals.ts", "node_modules/rxjs/src/internal/operators/combineLatest.ts", "node_modules/rxjs/src/internal/operators/combineLatestWith.ts", "node_modules/rxjs/src/internal/operators/debounce.ts", "node_modules/rxjs/src/internal/operators/debounceTime.ts", "node_modules/rxjs/src/internal/operators/defaultIfEmpty.ts", "node_modules/rxjs/src/internal/operators/take.ts", "node_modules/rxjs/src/internal/operators/ignoreElements.ts", "node_modules/rxjs/src/internal/operators/mapTo.ts", "node_modules/rxjs/src/internal/operators/delayWhen.ts", "node_modules/rxjs/src/internal/operators/delay.ts", "node_modules/rxjs/src/internal/operators/distinct.ts", "node_modules/rxjs/src/internal/operators/distinctUntilChanged.ts", "node_modules/rxjs/src/internal/operators/distinctUntilKeyChanged.ts", "node_modules/rxjs/src/internal/operators/throwIfEmpty.ts", "node_modules/rxjs/src/internal/operators/endWith.ts", "node_modules/rxjs/src/internal/operators/exhaustMap.ts", "node_modules/rxjs/src/internal/operators/finalize.ts", "node_modules/rxjs/src/internal/operators/first.ts", "node_modules/rxjs/src/internal/operators/takeLast.ts", "node_modules/rxjs/src/internal/operators/merge.ts", "node_modules/rxjs/src/internal/operators/mergeWith.ts", "node_modules/rxjs/src/internal/operators/repeat.ts", "node_modules/rxjs/src/internal/operators/scan.ts", "node_modules/rxjs/src/internal/operators/share.ts", "node_modules/rxjs/src/internal/operators/shareReplay.ts", "node_modules/rxjs/src/internal/operators/skip.ts", "node_modules/rxjs/src/internal/operators/skipUntil.ts", "node_modules/rxjs/src/internal/operators/startWith.ts", "node_modules/rxjs/src/internal/operators/switchMap.ts", "node_modules/rxjs/src/internal/operators/takeUntil.ts", "node_modules/rxjs/src/internal/operators/takeWhile.ts", "node_modules/rxjs/src/internal/operators/tap.ts", "node_modules/rxjs/src/internal/operators/throttle.ts", "node_modules/rxjs/src/internal/operators/throttleTime.ts", "node_modules/rxjs/src/internal/operators/withLatestFrom.ts", "node_modules/rxjs/src/internal/operators/zip.ts", "node_modules/rxjs/src/internal/operators/zipWith.ts", "src/templates/assets/javascripts/browser/document/index.ts", "src/templates/assets/javascripts/browser/element/_/index.ts", "src/templates/assets/javascripts/browser/element/focus/index.ts", "src/templates/assets/javascripts/browser/element/hover/index.ts", "src/templates/assets/javascripts/utilities/h/index.ts", "src/templates/assets/javascripts/utilities/round/index.ts", "src/templates/assets/javascripts/browser/script/index.ts", "src/templates/assets/javascripts/browser/element/size/_/index.ts", "src/templates/assets/javascripts/browser/element/size/content/index.ts", "src/templates/assets/javascripts/browser/element/offset/_/index.ts", "src/templates/assets/javascripts/browser/element/offset/content/index.ts", "src/templates/assets/javascripts/browser/element/visibility/index.ts", "src/templates/assets/javascripts/browser/toggle/index.ts", "src/templates/assets/javascripts/browser/keyboard/index.ts", "src/templates/assets/javascripts/browser/location/_/index.ts", "src/templates/assets/javascripts/browser/location/hash/index.ts", "src/templates/assets/javascripts/browser/media/index.ts", "src/templates/assets/javascripts/browser/request/index.ts", "src/templates/assets/javascripts/browser/viewport/offset/index.ts", "src/templates/assets/javascripts/browser/viewport/size/index.ts", "src/templates/assets/javascripts/browser/viewport/_/index.ts", "src/templates/assets/javascripts/browser/viewport/at/index.ts", "src/templates/assets/javascripts/browser/worker/index.ts", "src/templates/assets/javascripts/_/index.ts", "src/templates/assets/javascripts/components/_/index.ts", "src/templates/assets/javascripts/components/announce/index.ts", "src/templates/assets/javascripts/components/consent/index.ts", "src/templates/assets/javascripts/templates/tooltip/index.tsx", "src/templates/assets/javascripts/templates/annotation/index.tsx", "src/templates/assets/javascripts/templates/clipboard/index.tsx", "src/templates/assets/javascripts/templates/search/index.tsx", "src/templates/assets/javascripts/templates/source/index.tsx", "src/templates/assets/javascripts/templates/tabbed/index.tsx", "src/templates/assets/javascripts/templates/table/index.tsx", "src/templates/assets/javascripts/templates/version/index.tsx", "src/templates/assets/javascripts/components/tooltip2/index.ts", "src/templates/assets/javascripts/components/content/annotation/_/index.ts", "src/templates/assets/javascripts/components/content/annotation/list/index.ts", "src/templates/assets/javascripts/components/content/annotation/block/index.ts", "src/templates/assets/javascripts/components/content/code/_/index.ts", "src/templates/assets/javascripts/components/content/details/index.ts", "src/templates/assets/javascripts/components/content/link/index.ts", "src/templates/assets/javascripts/components/content/mermaid/index.css", "src/templates/assets/javascripts/components/content/mermaid/index.ts", "src/templates/assets/javascripts/components/content/table/index.ts", "src/templates/assets/javascripts/components/content/tabs/index.ts", "src/templates/assets/javascripts/components/content/_/index.ts", "src/templates/assets/javascripts/components/dialog/index.ts", "src/templates/assets/javascripts/components/tooltip/index.ts", "src/templates/assets/javascripts/components/header/_/index.ts", "src/templates/assets/javascripts/components/header/title/index.ts", "src/templates/assets/javascripts/components/main/index.ts", "src/templates/assets/javascripts/components/palette/index.ts", "src/templates/assets/javascripts/components/progress/index.ts", "src/templates/assets/javascripts/integrations/sitemap/index.ts", "src/templates/assets/javascripts/integrations/alternate/index.ts", "src/templates/assets/javascripts/integrations/clipboard/index.ts", "src/templates/assets/javascripts/integrations/instant/index.ts", "src/templates/assets/javascripts/integrations/search/highlighter/index.ts", "src/templates/assets/javascripts/integrations/search/worker/message/index.ts", "src/templates/assets/javascripts/integrations/search/worker/_/index.ts", "src/templates/assets/javascripts/integrations/version/findurl/index.ts", "src/templates/assets/javascripts/integrations/version/index.ts", "src/templates/assets/javascripts/components/search/query/index.ts", "src/templates/assets/javascripts/components/search/result/index.ts", "src/templates/assets/javascripts/components/search/share/index.ts", "src/templates/assets/javascripts/components/search/suggest/index.ts", "src/templates/assets/javascripts/components/search/_/index.ts", "src/templates/assets/javascripts/components/search/highlight/index.ts", "src/templates/assets/javascripts/components/sidebar/index.ts", "src/templates/assets/javascripts/components/source/facts/github/index.ts", "src/templates/assets/javascripts/components/source/facts/gitlab/index.ts", "src/templates/assets/javascripts/components/source/facts/_/index.ts", "src/templates/assets/javascripts/components/source/_/index.ts", "src/templates/assets/javascripts/components/tabs/index.ts", "src/templates/assets/javascripts/components/toc/index.ts", "src/templates/assets/javascripts/components/top/index.ts", "src/templates/assets/javascripts/patches/ellipsis/index.ts", "src/templates/assets/javascripts/patches/indeterminate/index.ts", "src/templates/assets/javascripts/patches/scrollfix/index.ts", "src/templates/assets/javascripts/patches/scrolllock/index.ts", "src/templates/assets/javascripts/polyfills/index.ts"], - "sourcesContent": ["(function (global, factory) {\n typeof exports === 'object' && typeof module !== 'undefined' ? factory() :\n typeof define === 'function' && define.amd ? define(factory) :\n (factory());\n}(this, (function () { 'use strict';\n\n /**\n * Applies the :focus-visible polyfill at the given scope.\n * A scope in this case is either the top-level Document or a Shadow Root.\n *\n * @param {(Document|ShadowRoot)} scope\n * @see https://github.com/WICG/focus-visible\n */\n function applyFocusVisiblePolyfill(scope) {\n var hadKeyboardEvent = true;\n var hadFocusVisibleRecently = false;\n var hadFocusVisibleRecentlyTimeout = null;\n\n var inputTypesAllowlist = {\n text: true,\n search: true,\n url: true,\n tel: true,\n email: true,\n password: true,\n number: true,\n date: true,\n month: true,\n week: true,\n time: true,\n datetime: true,\n 'datetime-local': true\n };\n\n /**\n * Helper function for legacy browsers and iframes which sometimes focus\n * elements like document, body, and non-interactive SVG.\n * @param {Element} el\n */\n function isValidFocusTarget(el) {\n if (\n el &&\n el !== document &&\n el.nodeName !== 'HTML' &&\n el.nodeName !== 'BODY' &&\n 'classList' in el &&\n 'contains' in el.classList\n ) {\n return true;\n }\n return false;\n }\n\n /**\n * Computes whether the given element should automatically trigger the\n * `focus-visible` class being added, i.e. whether it should always match\n * `:focus-visible` when focused.\n * @param {Element} el\n * @return {boolean}\n */\n function focusTriggersKeyboardModality(el) {\n var type = el.type;\n var tagName = el.tagName;\n\n if (tagName === 'INPUT' && inputTypesAllowlist[type] && !el.readOnly) {\n return true;\n }\n\n if (tagName === 'TEXTAREA' && !el.readOnly) {\n return true;\n }\n\n if (el.isContentEditable) {\n return true;\n }\n\n return false;\n }\n\n /**\n * Add the `focus-visible` class to the given element if it was not added by\n * the author.\n * @param {Element} el\n */\n function addFocusVisibleClass(el) {\n if (el.classList.contains('focus-visible')) {\n return;\n }\n el.classList.add('focus-visible');\n el.setAttribute('data-focus-visible-added', '');\n }\n\n /**\n * Remove the `focus-visible` class from the given element if it was not\n * originally added by the author.\n * @param {Element} el\n */\n function removeFocusVisibleClass(el) {\n if (!el.hasAttribute('data-focus-visible-added')) {\n return;\n }\n el.classList.remove('focus-visible');\n el.removeAttribute('data-focus-visible-added');\n }\n\n /**\n * If the most recent user interaction was via the keyboard;\n * and the key press did not include a meta, alt/option, or control key;\n * then the modality is keyboard. Otherwise, the modality is not keyboard.\n * Apply `focus-visible` to any current active element and keep track\n * of our keyboard modality state with `hadKeyboardEvent`.\n * @param {KeyboardEvent} e\n */\n function onKeyDown(e) {\n if (e.metaKey || e.altKey || e.ctrlKey) {\n return;\n }\n\n if (isValidFocusTarget(scope.activeElement)) {\n addFocusVisibleClass(scope.activeElement);\n }\n\n hadKeyboardEvent = true;\n }\n\n /**\n * If at any point a user clicks with a pointing device, ensure that we change\n * the modality away from keyboard.\n * This avoids the situation where a user presses a key on an already focused\n * element, and then clicks on a different element, focusing it with a\n * pointing device, while we still think we're in keyboard modality.\n * @param {Event} e\n */\n function onPointerDown(e) {\n hadKeyboardEvent = false;\n }\n\n /**\n * On `focus`, add the `focus-visible` class to the target if:\n * - the target received focus as a result of keyboard navigation, or\n * - the event target is an element that will likely require interaction\n * via the keyboard (e.g. a text box)\n * @param {Event} e\n */\n function onFocus(e) {\n // Prevent IE from focusing the document or HTML element.\n if (!isValidFocusTarget(e.target)) {\n return;\n }\n\n if (hadKeyboardEvent || focusTriggersKeyboardModality(e.target)) {\n addFocusVisibleClass(e.target);\n }\n }\n\n /**\n * On `blur`, remove the `focus-visible` class from the target.\n * @param {Event} e\n */\n function onBlur(e) {\n if (!isValidFocusTarget(e.target)) {\n return;\n }\n\n if (\n e.target.classList.contains('focus-visible') ||\n e.target.hasAttribute('data-focus-visible-added')\n ) {\n // To detect a tab/window switch, we look for a blur event followed\n // rapidly by a visibility change.\n // If we don't see a visibility change within 100ms, it's probably a\n // regular focus change.\n hadFocusVisibleRecently = true;\n window.clearTimeout(hadFocusVisibleRecentlyTimeout);\n hadFocusVisibleRecentlyTimeout = window.setTimeout(function() {\n hadFocusVisibleRecently = false;\n }, 100);\n removeFocusVisibleClass(e.target);\n }\n }\n\n /**\n * If the user changes tabs, keep track of whether or not the previously\n * focused element had .focus-visible.\n * @param {Event} e\n */\n function onVisibilityChange(e) {\n if (document.visibilityState === 'hidden') {\n // If the tab becomes active again, the browser will handle calling focus\n // on the element (Safari actually calls it twice).\n // If this tab change caused a blur on an element with focus-visible,\n // re-apply the class when the user switches back to the tab.\n if (hadFocusVisibleRecently) {\n hadKeyboardEvent = true;\n }\n addInitialPointerMoveListeners();\n }\n }\n\n /**\n * Add a group of listeners to detect usage of any pointing devices.\n * These listeners will be added when the polyfill first loads, and anytime\n * the window is blurred, so that they are active when the window regains\n * focus.\n */\n function addInitialPointerMoveListeners() {\n document.addEventListener('mousemove', onInitialPointerMove);\n document.addEventListener('mousedown', onInitialPointerMove);\n document.addEventListener('mouseup', onInitialPointerMove);\n document.addEventListener('pointermove', onInitialPointerMove);\n document.addEventListener('pointerdown', onInitialPointerMove);\n document.addEventListener('pointerup', onInitialPointerMove);\n document.addEventListener('touchmove', onInitialPointerMove);\n document.addEventListener('touchstart', onInitialPointerMove);\n document.addEventListener('touchend', onInitialPointerMove);\n }\n\n function removeInitialPointerMoveListeners() {\n document.removeEventListener('mousemove', onInitialPointerMove);\n document.removeEventListener('mousedown', onInitialPointerMove);\n document.removeEventListener('mouseup', onInitialPointerMove);\n document.removeEventListener('pointermove', onInitialPointerMove);\n document.removeEventListener('pointerdown', onInitialPointerMove);\n document.removeEventListener('pointerup', onInitialPointerMove);\n document.removeEventListener('touchmove', onInitialPointerMove);\n document.removeEventListener('touchstart', onInitialPointerMove);\n document.removeEventListener('touchend', onInitialPointerMove);\n }\n\n /**\n * When the polfyill first loads, assume the user is in keyboard modality.\n * If any event is received from a pointing device (e.g. mouse, pointer,\n * touch), turn off keyboard modality.\n * This accounts for situations where focus enters the page from the URL bar.\n * @param {Event} e\n */\n function onInitialPointerMove(e) {\n // Work around a Safari quirk that fires a mousemove on whenever the\n // window blurs, even if you're tabbing out of the page. \u00AF\\_(\u30C4)_/\u00AF\n if (e.target.nodeName && e.target.nodeName.toLowerCase() === 'html') {\n return;\n }\n\n hadKeyboardEvent = false;\n removeInitialPointerMoveListeners();\n }\n\n // For some kinds of state, we are interested in changes at the global scope\n // only. For example, global pointer input, global key presses and global\n // visibility change should affect the state at every scope:\n document.addEventListener('keydown', onKeyDown, true);\n document.addEventListener('mousedown', onPointerDown, true);\n document.addEventListener('pointerdown', onPointerDown, true);\n document.addEventListener('touchstart', onPointerDown, true);\n document.addEventListener('visibilitychange', onVisibilityChange, true);\n\n addInitialPointerMoveListeners();\n\n // For focus and blur, we specifically care about state changes in the local\n // scope. This is because focus / blur events that originate from within a\n // shadow root are not re-dispatched from the host element if it was already\n // the active element in its own scope:\n scope.addEventListener('focus', onFocus, true);\n scope.addEventListener('blur', onBlur, true);\n\n // We detect that a node is a ShadowRoot by ensuring that it is a\n // DocumentFragment and also has a host property. This check covers native\n // implementation and polyfill implementation transparently. If we only cared\n // about the native implementation, we could just check if the scope was\n // an instance of a ShadowRoot.\n if (scope.nodeType === Node.DOCUMENT_FRAGMENT_NODE && scope.host) {\n // Since a ShadowRoot is a special kind of DocumentFragment, it does not\n // have a root element to add a class to. So, we add this attribute to the\n // host element instead:\n scope.host.setAttribute('data-js-focus-visible', '');\n } else if (scope.nodeType === Node.DOCUMENT_NODE) {\n document.documentElement.classList.add('js-focus-visible');\n document.documentElement.setAttribute('data-js-focus-visible', '');\n }\n }\n\n // It is important to wrap all references to global window and document in\n // these checks to support server-side rendering use cases\n // @see https://github.com/WICG/focus-visible/issues/199\n if (typeof window !== 'undefined' && typeof document !== 'undefined') {\n // Make the polyfill helper globally available. This can be used as a signal\n // to interested libraries that wish to coordinate with the polyfill for e.g.,\n // applying the polyfill to a shadow root:\n window.applyFocusVisiblePolyfill = applyFocusVisiblePolyfill;\n\n // Notify interested libraries of the polyfill's presence, in case the\n // polyfill was loaded lazily:\n var event;\n\n try {\n event = new CustomEvent('focus-visible-polyfill-ready');\n } catch (error) {\n // IE11 does not support using CustomEvent as a constructor directly:\n event = document.createEvent('CustomEvent');\n event.initCustomEvent('focus-visible-polyfill-ready', false, false, {});\n }\n\n window.dispatchEvent(event);\n }\n\n if (typeof document !== 'undefined') {\n // Apply the polyfill to the global document, so that no JavaScript\n // coordination is required to use the polyfill in the top-level document:\n applyFocusVisiblePolyfill(document);\n }\n\n})));\n", "/*!\n * escape-html\n * Copyright(c) 2012-2013 TJ Holowaychuk\n * Copyright(c) 2015 Andreas Lubbe\n * Copyright(c) 2015 Tiancheng \"Timothy\" Gu\n * MIT Licensed\n */\n\n'use strict';\n\n/**\n * Module variables.\n * @private\n */\n\nvar matchHtmlRegExp = /[\"'&<>]/;\n\n/**\n * Module exports.\n * @public\n */\n\nmodule.exports = escapeHtml;\n\n/**\n * Escape special characters in the given string of html.\n *\n * @param {string} string The string to escape for inserting into HTML\n * @return {string}\n * @public\n */\n\nfunction escapeHtml(string) {\n var str = '' + string;\n var match = matchHtmlRegExp.exec(str);\n\n if (!match) {\n return str;\n }\n\n var escape;\n var html = '';\n var index = 0;\n var lastIndex = 0;\n\n for (index = match.index; index < str.length; index++) {\n switch (str.charCodeAt(index)) {\n case 34: // \"\n escape = '"';\n break;\n case 38: // &\n escape = '&';\n break;\n case 39: // '\n escape = ''';\n break;\n case 60: // <\n escape = '<';\n break;\n case 62: // >\n escape = '>';\n break;\n default:\n continue;\n }\n\n if (lastIndex !== index) {\n html += str.substring(lastIndex, index);\n }\n\n lastIndex = index + 1;\n html += escape;\n }\n\n return lastIndex !== index\n ? html + str.substring(lastIndex, index)\n : html;\n}\n", "/*!\n * clipboard.js v2.0.11\n * https://clipboardjs.com/\n *\n * Licensed MIT \u00A9 Zeno Rocha\n */\n(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"ClipboardJS\"] = factory();\n\telse\n\t\troot[\"ClipboardJS\"] = factory();\n})(this, function() {\nreturn /******/ (function() { // webpackBootstrap\n/******/ \tvar __webpack_modules__ = ({\n\n/***/ 686:\n/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {\n\n\"use strict\";\n\n// EXPORTS\n__webpack_require__.d(__webpack_exports__, {\n \"default\": function() { return /* binding */ clipboard; }\n});\n\n// EXTERNAL MODULE: ./node_modules/tiny-emitter/index.js\nvar tiny_emitter = __webpack_require__(279);\nvar tiny_emitter_default = /*#__PURE__*/__webpack_require__.n(tiny_emitter);\n// EXTERNAL MODULE: ./node_modules/good-listener/src/listen.js\nvar listen = __webpack_require__(370);\nvar listen_default = /*#__PURE__*/__webpack_require__.n(listen);\n// EXTERNAL MODULE: ./node_modules/select/src/select.js\nvar src_select = __webpack_require__(817);\nvar select_default = /*#__PURE__*/__webpack_require__.n(src_select);\n;// CONCATENATED MODULE: ./src/common/command.js\n/**\n * Executes a given operation type.\n * @param {String} type\n * @return {Boolean}\n */\nfunction command(type) {\n try {\n return document.execCommand(type);\n } catch (err) {\n return false;\n }\n}\n;// CONCATENATED MODULE: ./src/actions/cut.js\n\n\n/**\n * Cut action wrapper.\n * @param {String|HTMLElement} target\n * @return {String}\n */\n\nvar ClipboardActionCut = function ClipboardActionCut(target) {\n var selectedText = select_default()(target);\n command('cut');\n return selectedText;\n};\n\n/* harmony default export */ var actions_cut = (ClipboardActionCut);\n;// CONCATENATED MODULE: ./src/common/create-fake-element.js\n/**\n * Creates a fake textarea element with a value.\n * @param {String} value\n * @return {HTMLElement}\n */\nfunction createFakeElement(value) {\n var isRTL = document.documentElement.getAttribute('dir') === 'rtl';\n var fakeElement = document.createElement('textarea'); // Prevent zooming on iOS\n\n fakeElement.style.fontSize = '12pt'; // Reset box model\n\n fakeElement.style.border = '0';\n fakeElement.style.padding = '0';\n fakeElement.style.margin = '0'; // Move element out of screen horizontally\n\n fakeElement.style.position = 'absolute';\n fakeElement.style[isRTL ? 'right' : 'left'] = '-9999px'; // Move element to the same position vertically\n\n var yPosition = window.pageYOffset || document.documentElement.scrollTop;\n fakeElement.style.top = \"\".concat(yPosition, \"px\");\n fakeElement.setAttribute('readonly', '');\n fakeElement.value = value;\n return fakeElement;\n}\n;// CONCATENATED MODULE: ./src/actions/copy.js\n\n\n\n/**\n * Create fake copy action wrapper using a fake element.\n * @param {String} target\n * @param {Object} options\n * @return {String}\n */\n\nvar fakeCopyAction = function fakeCopyAction(value, options) {\n var fakeElement = createFakeElement(value);\n options.container.appendChild(fakeElement);\n var selectedText = select_default()(fakeElement);\n command('copy');\n fakeElement.remove();\n return selectedText;\n};\n/**\n * Copy action wrapper.\n * @param {String|HTMLElement} target\n * @param {Object} options\n * @return {String}\n */\n\n\nvar ClipboardActionCopy = function ClipboardActionCopy(target) {\n var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {\n container: document.body\n };\n var selectedText = '';\n\n if (typeof target === 'string') {\n selectedText = fakeCopyAction(target, options);\n } else if (target instanceof HTMLInputElement && !['text', 'search', 'url', 'tel', 'password'].includes(target === null || target === void 0 ? void 0 : target.type)) {\n // If input type doesn't support `setSelectionRange`. Simulate it. https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/setSelectionRange\n selectedText = fakeCopyAction(target.value, options);\n } else {\n selectedText = select_default()(target);\n command('copy');\n }\n\n return selectedText;\n};\n\n/* harmony default export */ var actions_copy = (ClipboardActionCopy);\n;// CONCATENATED MODULE: ./src/actions/default.js\nfunction _typeof(obj) { \"@babel/helpers - typeof\"; if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }; } return _typeof(obj); }\n\n\n\n/**\n * Inner function which performs selection from either `text` or `target`\n * properties and then executes copy or cut operations.\n * @param {Object} options\n */\n\nvar ClipboardActionDefault = function ClipboardActionDefault() {\n var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n // Defines base properties passed from constructor.\n var _options$action = options.action,\n action = _options$action === void 0 ? 'copy' : _options$action,\n container = options.container,\n target = options.target,\n text = options.text; // Sets the `action` to be performed which can be either 'copy' or 'cut'.\n\n if (action !== 'copy' && action !== 'cut') {\n throw new Error('Invalid \"action\" value, use either \"copy\" or \"cut\"');\n } // Sets the `target` property using an element that will be have its content copied.\n\n\n if (target !== undefined) {\n if (target && _typeof(target) === 'object' && target.nodeType === 1) {\n if (action === 'copy' && target.hasAttribute('disabled')) {\n throw new Error('Invalid \"target\" attribute. Please use \"readonly\" instead of \"disabled\" attribute');\n }\n\n if (action === 'cut' && (target.hasAttribute('readonly') || target.hasAttribute('disabled'))) {\n throw new Error('Invalid \"target\" attribute. You can\\'t cut text from elements with \"readonly\" or \"disabled\" attributes');\n }\n } else {\n throw new Error('Invalid \"target\" value, use a valid Element');\n }\n } // Define selection strategy based on `text` property.\n\n\n if (text) {\n return actions_copy(text, {\n container: container\n });\n } // Defines which selection strategy based on `target` property.\n\n\n if (target) {\n return action === 'cut' ? actions_cut(target) : actions_copy(target, {\n container: container\n });\n }\n};\n\n/* harmony default export */ var actions_default = (ClipboardActionDefault);\n;// CONCATENATED MODULE: ./src/clipboard.js\nfunction clipboard_typeof(obj) { \"@babel/helpers - typeof\"; if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") { clipboard_typeof = function _typeof(obj) { return typeof obj; }; } else { clipboard_typeof = function _typeof(obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }; } return clipboard_typeof(obj); }\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\n\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }\n\nfunction _inherits(subClass, superClass) { if (typeof superClass !== \"function\" && superClass !== null) { throw new TypeError(\"Super expression must either be null or a function\"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }\n\nfunction _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }\n\nfunction _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }\n\nfunction _possibleConstructorReturn(self, call) { if (call && (clipboard_typeof(call) === \"object\" || typeof call === \"function\")) { return call; } return _assertThisInitialized(self); }\n\nfunction _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); } return self; }\n\nfunction _isNativeReflectConstruct() { if (typeof Reflect === \"undefined\" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === \"function\") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } }\n\nfunction _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }\n\n\n\n\n\n\n/**\n * Helper function to retrieve attribute value.\n * @param {String} suffix\n * @param {Element} element\n */\n\nfunction getAttributeValue(suffix, element) {\n var attribute = \"data-clipboard-\".concat(suffix);\n\n if (!element.hasAttribute(attribute)) {\n return;\n }\n\n return element.getAttribute(attribute);\n}\n/**\n * Base class which takes one or more elements, adds event listeners to them,\n * and instantiates a new `ClipboardAction` on each click.\n */\n\n\nvar Clipboard = /*#__PURE__*/function (_Emitter) {\n _inherits(Clipboard, _Emitter);\n\n var _super = _createSuper(Clipboard);\n\n /**\n * @param {String|HTMLElement|HTMLCollection|NodeList} trigger\n * @param {Object} options\n */\n function Clipboard(trigger, options) {\n var _this;\n\n _classCallCheck(this, Clipboard);\n\n _this = _super.call(this);\n\n _this.resolveOptions(options);\n\n _this.listenClick(trigger);\n\n return _this;\n }\n /**\n * Defines if attributes would be resolved using internal setter functions\n * or custom functions that were passed in the constructor.\n * @param {Object} options\n */\n\n\n _createClass(Clipboard, [{\n key: \"resolveOptions\",\n value: function resolveOptions() {\n var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n this.action = typeof options.action === 'function' ? options.action : this.defaultAction;\n this.target = typeof options.target === 'function' ? options.target : this.defaultTarget;\n this.text = typeof options.text === 'function' ? options.text : this.defaultText;\n this.container = clipboard_typeof(options.container) === 'object' ? options.container : document.body;\n }\n /**\n * Adds a click event listener to the passed trigger.\n * @param {String|HTMLElement|HTMLCollection|NodeList} trigger\n */\n\n }, {\n key: \"listenClick\",\n value: function listenClick(trigger) {\n var _this2 = this;\n\n this.listener = listen_default()(trigger, 'click', function (e) {\n return _this2.onClick(e);\n });\n }\n /**\n * Defines a new `ClipboardAction` on each click event.\n * @param {Event} e\n */\n\n }, {\n key: \"onClick\",\n value: function onClick(e) {\n var trigger = e.delegateTarget || e.currentTarget;\n var action = this.action(trigger) || 'copy';\n var text = actions_default({\n action: action,\n container: this.container,\n target: this.target(trigger),\n text: this.text(trigger)\n }); // Fires an event based on the copy operation result.\n\n this.emit(text ? 'success' : 'error', {\n action: action,\n text: text,\n trigger: trigger,\n clearSelection: function clearSelection() {\n if (trigger) {\n trigger.focus();\n }\n\n window.getSelection().removeAllRanges();\n }\n });\n }\n /**\n * Default `action` lookup function.\n * @param {Element} trigger\n */\n\n }, {\n key: \"defaultAction\",\n value: function defaultAction(trigger) {\n return getAttributeValue('action', trigger);\n }\n /**\n * Default `target` lookup function.\n * @param {Element} trigger\n */\n\n }, {\n key: \"defaultTarget\",\n value: function defaultTarget(trigger) {\n var selector = getAttributeValue('target', trigger);\n\n if (selector) {\n return document.querySelector(selector);\n }\n }\n /**\n * Allow fire programmatically a copy action\n * @param {String|HTMLElement} target\n * @param {Object} options\n * @returns Text copied.\n */\n\n }, {\n key: \"defaultText\",\n\n /**\n * Default `text` lookup function.\n * @param {Element} trigger\n */\n value: function defaultText(trigger) {\n return getAttributeValue('text', trigger);\n }\n /**\n * Destroy lifecycle.\n */\n\n }, {\n key: \"destroy\",\n value: function destroy() {\n this.listener.destroy();\n }\n }], [{\n key: \"copy\",\n value: function copy(target) {\n var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {\n container: document.body\n };\n return actions_copy(target, options);\n }\n /**\n * Allow fire programmatically a cut action\n * @param {String|HTMLElement} target\n * @returns Text cutted.\n */\n\n }, {\n key: \"cut\",\n value: function cut(target) {\n return actions_cut(target);\n }\n /**\n * Returns the support of the given action, or all actions if no action is\n * given.\n * @param {String} [action]\n */\n\n }, {\n key: \"isSupported\",\n value: function isSupported() {\n var action = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ['copy', 'cut'];\n var actions = typeof action === 'string' ? [action] : action;\n var support = !!document.queryCommandSupported;\n actions.forEach(function (action) {\n support = support && !!document.queryCommandSupported(action);\n });\n return support;\n }\n }]);\n\n return Clipboard;\n}((tiny_emitter_default()));\n\n/* harmony default export */ var clipboard = (Clipboard);\n\n/***/ }),\n\n/***/ 828:\n/***/ (function(module) {\n\nvar DOCUMENT_NODE_TYPE = 9;\n\n/**\n * A polyfill for Element.matches()\n */\nif (typeof Element !== 'undefined' && !Element.prototype.matches) {\n var proto = Element.prototype;\n\n proto.matches = proto.matchesSelector ||\n proto.mozMatchesSelector ||\n proto.msMatchesSelector ||\n proto.oMatchesSelector ||\n proto.webkitMatchesSelector;\n}\n\n/**\n * Finds the closest parent that matches a selector.\n *\n * @param {Element} element\n * @param {String} selector\n * @return {Function}\n */\nfunction closest (element, selector) {\n while (element && element.nodeType !== DOCUMENT_NODE_TYPE) {\n if (typeof element.matches === 'function' &&\n element.matches(selector)) {\n return element;\n }\n element = element.parentNode;\n }\n}\n\nmodule.exports = closest;\n\n\n/***/ }),\n\n/***/ 438:\n/***/ (function(module, __unused_webpack_exports, __webpack_require__) {\n\nvar closest = __webpack_require__(828);\n\n/**\n * Delegates event to a selector.\n *\n * @param {Element} element\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @param {Boolean} useCapture\n * @return {Object}\n */\nfunction _delegate(element, selector, type, callback, useCapture) {\n var listenerFn = listener.apply(this, arguments);\n\n element.addEventListener(type, listenerFn, useCapture);\n\n return {\n destroy: function() {\n element.removeEventListener(type, listenerFn, useCapture);\n }\n }\n}\n\n/**\n * Delegates event to a selector.\n *\n * @param {Element|String|Array} [elements]\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @param {Boolean} useCapture\n * @return {Object}\n */\nfunction delegate(elements, selector, type, callback, useCapture) {\n // Handle the regular Element usage\n if (typeof elements.addEventListener === 'function') {\n return _delegate.apply(null, arguments);\n }\n\n // Handle Element-less usage, it defaults to global delegation\n if (typeof type === 'function') {\n // Use `document` as the first parameter, then apply arguments\n // This is a short way to .unshift `arguments` without running into deoptimizations\n return _delegate.bind(null, document).apply(null, arguments);\n }\n\n // Handle Selector-based usage\n if (typeof elements === 'string') {\n elements = document.querySelectorAll(elements);\n }\n\n // Handle Array-like based usage\n return Array.prototype.map.call(elements, function (element) {\n return _delegate(element, selector, type, callback, useCapture);\n });\n}\n\n/**\n * Finds closest match and invokes callback.\n *\n * @param {Element} element\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @return {Function}\n */\nfunction listener(element, selector, type, callback) {\n return function(e) {\n e.delegateTarget = closest(e.target, selector);\n\n if (e.delegateTarget) {\n callback.call(element, e);\n }\n }\n}\n\nmodule.exports = delegate;\n\n\n/***/ }),\n\n/***/ 879:\n/***/ (function(__unused_webpack_module, exports) {\n\n/**\n * Check if argument is a HTML element.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.node = function(value) {\n return value !== undefined\n && value instanceof HTMLElement\n && value.nodeType === 1;\n};\n\n/**\n * Check if argument is a list of HTML elements.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.nodeList = function(value) {\n var type = Object.prototype.toString.call(value);\n\n return value !== undefined\n && (type === '[object NodeList]' || type === '[object HTMLCollection]')\n && ('length' in value)\n && (value.length === 0 || exports.node(value[0]));\n};\n\n/**\n * Check if argument is a string.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.string = function(value) {\n return typeof value === 'string'\n || value instanceof String;\n};\n\n/**\n * Check if argument is a function.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.fn = function(value) {\n var type = Object.prototype.toString.call(value);\n\n return type === '[object Function]';\n};\n\n\n/***/ }),\n\n/***/ 370:\n/***/ (function(module, __unused_webpack_exports, __webpack_require__) {\n\nvar is = __webpack_require__(879);\nvar delegate = __webpack_require__(438);\n\n/**\n * Validates all params and calls the right\n * listener function based on its target type.\n *\n * @param {String|HTMLElement|HTMLCollection|NodeList} target\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listen(target, type, callback) {\n if (!target && !type && !callback) {\n throw new Error('Missing required arguments');\n }\n\n if (!is.string(type)) {\n throw new TypeError('Second argument must be a String');\n }\n\n if (!is.fn(callback)) {\n throw new TypeError('Third argument must be a Function');\n }\n\n if (is.node(target)) {\n return listenNode(target, type, callback);\n }\n else if (is.nodeList(target)) {\n return listenNodeList(target, type, callback);\n }\n else if (is.string(target)) {\n return listenSelector(target, type, callback);\n }\n else {\n throw new TypeError('First argument must be a String, HTMLElement, HTMLCollection, or NodeList');\n }\n}\n\n/**\n * Adds an event listener to a HTML element\n * and returns a remove listener function.\n *\n * @param {HTMLElement} node\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenNode(node, type, callback) {\n node.addEventListener(type, callback);\n\n return {\n destroy: function() {\n node.removeEventListener(type, callback);\n }\n }\n}\n\n/**\n * Add an event listener to a list of HTML elements\n * and returns a remove listener function.\n *\n * @param {NodeList|HTMLCollection} nodeList\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenNodeList(nodeList, type, callback) {\n Array.prototype.forEach.call(nodeList, function(node) {\n node.addEventListener(type, callback);\n });\n\n return {\n destroy: function() {\n Array.prototype.forEach.call(nodeList, function(node) {\n node.removeEventListener(type, callback);\n });\n }\n }\n}\n\n/**\n * Add an event listener to a selector\n * and returns a remove listener function.\n *\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenSelector(selector, type, callback) {\n return delegate(document.body, selector, type, callback);\n}\n\nmodule.exports = listen;\n\n\n/***/ }),\n\n/***/ 817:\n/***/ (function(module) {\n\nfunction select(element) {\n var selectedText;\n\n if (element.nodeName === 'SELECT') {\n element.focus();\n\n selectedText = element.value;\n }\n else if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') {\n var isReadOnly = element.hasAttribute('readonly');\n\n if (!isReadOnly) {\n element.setAttribute('readonly', '');\n }\n\n element.select();\n element.setSelectionRange(0, element.value.length);\n\n if (!isReadOnly) {\n element.removeAttribute('readonly');\n }\n\n selectedText = element.value;\n }\n else {\n if (element.hasAttribute('contenteditable')) {\n element.focus();\n }\n\n var selection = window.getSelection();\n var range = document.createRange();\n\n range.selectNodeContents(element);\n selection.removeAllRanges();\n selection.addRange(range);\n\n selectedText = selection.toString();\n }\n\n return selectedText;\n}\n\nmodule.exports = select;\n\n\n/***/ }),\n\n/***/ 279:\n/***/ (function(module) {\n\nfunction E () {\n // Keep this empty so it's easier to inherit from\n // (via https://github.com/lipsmack from https://github.com/scottcorgan/tiny-emitter/issues/3)\n}\n\nE.prototype = {\n on: function (name, callback, ctx) {\n var e = this.e || (this.e = {});\n\n (e[name] || (e[name] = [])).push({\n fn: callback,\n ctx: ctx\n });\n\n return this;\n },\n\n once: function (name, callback, ctx) {\n var self = this;\n function listener () {\n self.off(name, listener);\n callback.apply(ctx, arguments);\n };\n\n listener._ = callback\n return this.on(name, listener, ctx);\n },\n\n emit: function (name) {\n var data = [].slice.call(arguments, 1);\n var evtArr = ((this.e || (this.e = {}))[name] || []).slice();\n var i = 0;\n var len = evtArr.length;\n\n for (i; i < len; i++) {\n evtArr[i].fn.apply(evtArr[i].ctx, data);\n }\n\n return this;\n },\n\n off: function (name, callback) {\n var e = this.e || (this.e = {});\n var evts = e[name];\n var liveEvents = [];\n\n if (evts && callback) {\n for (var i = 0, len = evts.length; i < len; i++) {\n if (evts[i].fn !== callback && evts[i].fn._ !== callback)\n liveEvents.push(evts[i]);\n }\n }\n\n // Remove event from queue to prevent memory leak\n // Suggested by https://github.com/lazd\n // Ref: https://github.com/scottcorgan/tiny-emitter/commit/c6ebfaa9bc973b33d110a84a307742b7cf94c953#commitcomment-5024910\n\n (liveEvents.length)\n ? e[name] = liveEvents\n : delete e[name];\n\n return this;\n }\n};\n\nmodule.exports = E;\nmodule.exports.TinyEmitter = E;\n\n\n/***/ })\n\n/******/ \t});\n/************************************************************************/\n/******/ \t// The module cache\n/******/ \tvar __webpack_module_cache__ = {};\n/******/ \t\n/******/ \t// The require function\n/******/ \tfunction __webpack_require__(moduleId) {\n/******/ \t\t// Check if module is in cache\n/******/ \t\tif(__webpack_module_cache__[moduleId]) {\n/******/ \t\t\treturn __webpack_module_cache__[moduleId].exports;\n/******/ \t\t}\n/******/ \t\t// Create a new module (and put it into the cache)\n/******/ \t\tvar module = __webpack_module_cache__[moduleId] = {\n/******/ \t\t\t// no module.id needed\n/******/ \t\t\t// no module.loaded needed\n/******/ \t\t\texports: {}\n/******/ \t\t};\n/******/ \t\n/******/ \t\t// Execute the module function\n/******/ \t\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n/******/ \t\n/******/ \t\t// Return the exports of the module\n/******/ \t\treturn module.exports;\n/******/ \t}\n/******/ \t\n/************************************************************************/\n/******/ \t/* webpack/runtime/compat get default export */\n/******/ \t!function() {\n/******/ \t\t// getDefaultExport function for compatibility with non-harmony modules\n/******/ \t\t__webpack_require__.n = function(module) {\n/******/ \t\t\tvar getter = module && module.__esModule ?\n/******/ \t\t\t\tfunction() { return module['default']; } :\n/******/ \t\t\t\tfunction() { return module; };\n/******/ \t\t\t__webpack_require__.d(getter, { a: getter });\n/******/ \t\t\treturn getter;\n/******/ \t\t};\n/******/ \t}();\n/******/ \t\n/******/ \t/* webpack/runtime/define property getters */\n/******/ \t!function() {\n/******/ \t\t// define getter functions for harmony exports\n/******/ \t\t__webpack_require__.d = function(exports, definition) {\n/******/ \t\t\tfor(var key in definition) {\n/******/ \t\t\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n/******/ \t\t\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n/******/ \t\t\t\t}\n/******/ \t\t\t}\n/******/ \t\t};\n/******/ \t}();\n/******/ \t\n/******/ \t/* webpack/runtime/hasOwnProperty shorthand */\n/******/ \t!function() {\n/******/ \t\t__webpack_require__.o = function(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }\n/******/ \t}();\n/******/ \t\n/************************************************************************/\n/******/ \t// module exports must be returned from runtime so entry inlining is disabled\n/******/ \t// startup\n/******/ \t// Load entry module and return exports\n/******/ \treturn __webpack_require__(686);\n/******/ })()\n.default;\n});", "/*\n * Copyright (c) 2016-2025 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport \"focus-visible\"\n\nimport {\n EMPTY,\n NEVER,\n Observable,\n Subject,\n defer,\n delay,\n filter,\n map,\n merge,\n mergeWith,\n shareReplay,\n switchMap\n} from \"rxjs\"\n\nimport { configuration, feature } from \"./_\"\nimport {\n at,\n getActiveElement,\n getOptionalElement,\n requestJSON,\n setLocation,\n setToggle,\n watchDocument,\n watchKeyboard,\n watchLocation,\n watchLocationTarget,\n watchMedia,\n watchPrint,\n watchScript,\n watchViewport\n} from \"./browser\"\nimport {\n getComponentElement,\n getComponentElements,\n mountAnnounce,\n mountBackToTop,\n mountConsent,\n mountContent,\n mountDialog,\n mountHeader,\n mountHeaderTitle,\n mountPalette,\n mountProgress,\n mountSearch,\n mountSearchHiglight,\n mountSidebar,\n mountSource,\n mountTableOfContents,\n mountTabs,\n watchHeader,\n watchMain\n} from \"./components\"\nimport {\n SearchIndex,\n fetchSitemap,\n setupAlternate,\n setupClipboardJS,\n setupInstantNavigation,\n setupVersionSelector\n} from \"./integrations\"\nimport {\n patchEllipsis,\n patchIndeterminate,\n patchScrollfix,\n patchScrolllock\n} from \"./patches\"\nimport \"./polyfills\"\n\n/* ----------------------------------------------------------------------------\n * Functions - @todo refactor\n * ------------------------------------------------------------------------- */\n\n/**\n * Fetch search index\n *\n * @returns Search index observable\n */\nfunction fetchSearchIndex(): Observable {\n if (location.protocol === \"file:\") {\n return watchScript(\n `${new URL(\"search/search_index.js\", config.base)}`\n )\n .pipe(\n // @ts-ignore - @todo fix typings\n map(() => __index),\n shareReplay(1)\n )\n } else {\n return requestJSON(\n new URL(\"search/search_index.json\", config.base)\n )\n }\n}\n\n/* ----------------------------------------------------------------------------\n * Application\n * ------------------------------------------------------------------------- */\n\n/* Yay, JavaScript is available */\ndocument.documentElement.classList.remove(\"no-js\")\ndocument.documentElement.classList.add(\"js\")\n\n/* Set up navigation observables and subjects */\nconst document$ = watchDocument()\nconst location$ = watchLocation()\nconst target$ = watchLocationTarget(location$)\nconst keyboard$ = watchKeyboard()\n\n/* Set up media observables */\nconst viewport$ = watchViewport()\nconst tablet$ = watchMedia(\"(min-width: 60em)\")\nconst screen$ = watchMedia(\"(min-width: 76.25em)\")\nconst print$ = watchPrint()\n\n/* Retrieve search index, if search is enabled */\nconst config = configuration()\nconst index$ = document.forms.namedItem(\"search\")\n ? fetchSearchIndex()\n : NEVER\n\n/* Set up Clipboard.js integration */\nconst alert$ = new Subject()\nsetupClipboardJS({ alert$ })\n\n/* Set up language selector */\nsetupAlternate({ document$ })\n\n/* Set up progress indicator */\nconst progress$ = new Subject()\n\n/* Set up sitemap for instant navigation and previews */\nconst sitemap$ = fetchSitemap(config.base)\n\n/* Set up instant navigation, if enabled */\nif (feature(\"navigation.instant\"))\n setupInstantNavigation({ sitemap$, location$, viewport$, progress$ })\n .subscribe(document$)\n\n/* Set up version selector */\nif (config.version?.provider === \"mike\")\n setupVersionSelector({ document$ })\n\n/* Always close drawer and search on navigation */\nmerge(location$, target$)\n .pipe(\n delay(125)\n )\n .subscribe(() => {\n setToggle(\"drawer\", false)\n setToggle(\"search\", false)\n })\n\n/* Set up global keyboard handlers */\nkeyboard$\n .pipe(\n filter(({ mode }) => mode === \"global\")\n )\n .subscribe(key => {\n switch (key.type) {\n\n /* Go to previous page */\n case \"p\":\n case \",\":\n const prev = getOptionalElement(\"link[rel=prev]\")\n if (typeof prev !== \"undefined\")\n setLocation(prev)\n break\n\n /* Go to next page */\n case \"n\":\n case \".\":\n const next = getOptionalElement(\"link[rel=next]\")\n if (typeof next !== \"undefined\")\n setLocation(next)\n break\n\n /* Expand navigation, see https://bit.ly/3ZjG5io */\n case \"Enter\":\n const active = getActiveElement()\n if (active instanceof HTMLLabelElement)\n active.click()\n }\n })\n\n/* Set up patches */\npatchEllipsis({ viewport$, document$ })\npatchIndeterminate({ document$, tablet$ })\npatchScrollfix({ document$ })\npatchScrolllock({ viewport$, tablet$ })\n\n/* Set up header and main area observable */\nconst header$ = watchHeader(getComponentElement(\"header\"), { viewport$ })\nconst main$ = document$\n .pipe(\n map(() => getComponentElement(\"main\")),\n switchMap(el => watchMain(el, { viewport$, header$ })),\n shareReplay(1)\n )\n\n/* Set up control component observables */\nconst control$ = merge(\n\n /* Consent */\n ...getComponentElements(\"consent\")\n .map(el => mountConsent(el, { target$ })),\n\n /* Dialog */\n ...getComponentElements(\"dialog\")\n .map(el => mountDialog(el, { alert$ })),\n\n /* Color palette */\n ...getComponentElements(\"palette\")\n .map(el => mountPalette(el)),\n\n /* Progress bar */\n ...getComponentElements(\"progress\")\n .map(el => mountProgress(el, { progress$ })),\n\n /* Search */\n ...getComponentElements(\"search\")\n .map(el => mountSearch(el, { index$, keyboard$ })),\n\n /* Repository information */\n ...getComponentElements(\"source\")\n .map(el => mountSource(el))\n)\n\n/* Set up content component observables */\nconst content$ = defer(() => merge(\n\n /* Announcement bar */\n ...getComponentElements(\"announce\")\n .map(el => mountAnnounce(el)),\n\n /* Content */\n ...getComponentElements(\"content\")\n .map(el => mountContent(el, { sitemap$, viewport$, target$, print$ })),\n\n /* Search highlighting */\n ...getComponentElements(\"content\")\n .map(el => feature(\"search.highlight\")\n ? mountSearchHiglight(el, { index$, location$ })\n : EMPTY\n ),\n\n /* Header */\n ...getComponentElements(\"header\")\n .map(el => mountHeader(el, { viewport$, header$, main$ })),\n\n /* Header title */\n ...getComponentElements(\"header-title\")\n .map(el => mountHeaderTitle(el, { viewport$, header$ })),\n\n /* Sidebar */\n ...getComponentElements(\"sidebar\")\n .map(el => el.getAttribute(\"data-md-type\") === \"navigation\"\n ? at(screen$, () => mountSidebar(el, { viewport$, header$, main$ }))\n : at(tablet$, () => mountSidebar(el, { viewport$, header$, main$ }))\n ),\n\n /* Navigation tabs */\n ...getComponentElements(\"tabs\")\n .map(el => mountTabs(el, { viewport$, header$ })),\n\n /* Table of contents */\n ...getComponentElements(\"toc\")\n .map(el => mountTableOfContents(el, {\n viewport$, header$, main$, target$\n })),\n\n /* Back-to-top button */\n ...getComponentElements(\"top\")\n .map(el => mountBackToTop(el, { viewport$, header$, main$, target$ }))\n))\n\n/* Set up component observables */\nconst component$ = document$\n .pipe(\n switchMap(() => content$),\n mergeWith(control$),\n shareReplay(1)\n )\n\n/* Subscribe to all components */\ncomponent$.subscribe()\n\n/* ----------------------------------------------------------------------------\n * Exports\n * ------------------------------------------------------------------------- */\n\nwindow.document$ = document$ /* Document observable */\nwindow.location$ = location$ /* Location subject */\nwindow.target$ = target$ /* Location target observable */\nwindow.keyboard$ = keyboard$ /* Keyboard observable */\nwindow.viewport$ = viewport$ /* Viewport observable */\nwindow.tablet$ = tablet$ /* Media tablet observable */\nwindow.screen$ = screen$ /* Media screen observable */\nwindow.print$ = print$ /* Media print observable */\nwindow.alert$ = alert$ /* Alert subject */\nwindow.progress$ = progress$ /* Progress indicator subject */\nwindow.component$ = component$ /* Component observable */\n", "/******************************************************************************\nCopyright (c) Microsoft Corporation.\n\nPermission to use, copy, modify, and/or distribute this software for any\npurpose with or without fee is hereby granted.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\nAND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\nLOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\nOTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\nPERFORMANCE OF THIS SOFTWARE.\n***************************************************************************** */\n/* global Reflect, Promise, SuppressedError, Symbol, Iterator */\n\nvar extendStatics = function(d, b) {\n extendStatics = Object.setPrototypeOf ||\n ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||\n function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };\n return extendStatics(d, b);\n};\n\nexport function __extends(d, b) {\n if (typeof b !== \"function\" && b !== null)\n throw new TypeError(\"Class extends value \" + String(b) + \" is not a constructor or null\");\n extendStatics(d, b);\n function __() { this.constructor = d; }\n d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());\n}\n\nexport var __assign = function() {\n __assign = Object.assign || function __assign(t) {\n for (var s, i = 1, n = arguments.length; i < n; i++) {\n s = arguments[i];\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];\n }\n return t;\n }\n return __assign.apply(this, arguments);\n}\n\nexport function __rest(s, e) {\n var t = {};\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)\n t[p] = s[p];\n if (s != null && typeof Object.getOwnPropertySymbols === \"function\")\n for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {\n if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))\n t[p[i]] = s[p[i]];\n }\n return t;\n}\n\nexport function __decorate(decorators, target, key, desc) {\n var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\n if (typeof Reflect === \"object\" && typeof Reflect.decorate === \"function\") r = Reflect.decorate(decorators, target, key, desc);\n else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\n return c > 3 && r && Object.defineProperty(target, key, r), r;\n}\n\nexport function __param(paramIndex, decorator) {\n return function (target, key) { decorator(target, key, paramIndex); }\n}\n\nexport function __esDecorate(ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {\n function accept(f) { if (f !== void 0 && typeof f !== \"function\") throw new TypeError(\"Function expected\"); return f; }\n var kind = contextIn.kind, key = kind === \"getter\" ? \"get\" : kind === \"setter\" ? \"set\" : \"value\";\n var target = !descriptorIn && ctor ? contextIn[\"static\"] ? ctor : ctor.prototype : null;\n var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});\n var _, done = false;\n for (var i = decorators.length - 1; i >= 0; i--) {\n var context = {};\n for (var p in contextIn) context[p] = p === \"access\" ? {} : contextIn[p];\n for (var p in contextIn.access) context.access[p] = contextIn.access[p];\n context.addInitializer = function (f) { if (done) throw new TypeError(\"Cannot add initializers after decoration has completed\"); extraInitializers.push(accept(f || null)); };\n var result = (0, decorators[i])(kind === \"accessor\" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);\n if (kind === \"accessor\") {\n if (result === void 0) continue;\n if (result === null || typeof result !== \"object\") throw new TypeError(\"Object expected\");\n if (_ = accept(result.get)) descriptor.get = _;\n if (_ = accept(result.set)) descriptor.set = _;\n if (_ = accept(result.init)) initializers.unshift(_);\n }\n else if (_ = accept(result)) {\n if (kind === \"field\") initializers.unshift(_);\n else descriptor[key] = _;\n }\n }\n if (target) Object.defineProperty(target, contextIn.name, descriptor);\n done = true;\n};\n\nexport function __runInitializers(thisArg, initializers, value) {\n var useValue = arguments.length > 2;\n for (var i = 0; i < initializers.length; i++) {\n value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);\n }\n return useValue ? value : void 0;\n};\n\nexport function __propKey(x) {\n return typeof x === \"symbol\" ? x : \"\".concat(x);\n};\n\nexport function __setFunctionName(f, name, prefix) {\n if (typeof name === \"symbol\") name = name.description ? \"[\".concat(name.description, \"]\") : \"\";\n return Object.defineProperty(f, \"name\", { configurable: true, value: prefix ? \"\".concat(prefix, \" \", name) : name });\n};\n\nexport function __metadata(metadataKey, metadataValue) {\n if (typeof Reflect === \"object\" && typeof Reflect.metadata === \"function\") return Reflect.metadata(metadataKey, metadataValue);\n}\n\nexport function __awaiter(thisArg, _arguments, P, generator) {\n function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\n return new (P || (P = Promise))(function (resolve, reject) {\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\n function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\n step((generator = generator.apply(thisArg, _arguments || [])).next());\n });\n}\n\nexport function __generator(thisArg, body) {\n var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === \"function\" ? Iterator : Object).prototype);\n return g.next = verb(0), g[\"throw\"] = verb(1), g[\"return\"] = verb(2), typeof Symbol === \"function\" && (g[Symbol.iterator] = function() { return this; }), g;\n function verb(n) { return function (v) { return step([n, v]); }; }\n function step(op) {\n if (f) throw new TypeError(\"Generator is already executing.\");\n while (g && (g = 0, op[0] && (_ = 0)), _) try {\n if (f = 1, y && (t = op[0] & 2 ? y[\"return\"] : op[0] ? y[\"throw\"] || ((t = y[\"return\"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;\n if (y = 0, t) op = [op[0] & 2, t.value];\n switch (op[0]) {\n case 0: case 1: t = op; break;\n case 4: _.label++; return { value: op[1], done: false };\n case 5: _.label++; y = op[1]; op = [0]; continue;\n case 7: op = _.ops.pop(); _.trys.pop(); continue;\n default:\n if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }\n if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }\n if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }\n if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }\n if (t[2]) _.ops.pop();\n _.trys.pop(); continue;\n }\n op = body.call(thisArg, _);\n } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }\n if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };\n }\n}\n\nexport var __createBinding = Object.create ? (function(o, m, k, k2) {\n if (k2 === undefined) k2 = k;\n var desc = Object.getOwnPropertyDescriptor(m, k);\n if (!desc || (\"get\" in desc ? !m.__esModule : desc.writable || desc.configurable)) {\n desc = { enumerable: true, get: function() { return m[k]; } };\n }\n Object.defineProperty(o, k2, desc);\n}) : (function(o, m, k, k2) {\n if (k2 === undefined) k2 = k;\n o[k2] = m[k];\n});\n\nexport function __exportStar(m, o) {\n for (var p in m) if (p !== \"default\" && !Object.prototype.hasOwnProperty.call(o, p)) __createBinding(o, m, p);\n}\n\nexport function __values(o) {\n var s = typeof Symbol === \"function\" && Symbol.iterator, m = s && o[s], i = 0;\n if (m) return m.call(o);\n if (o && typeof o.length === \"number\") return {\n next: function () {\n if (o && i >= o.length) o = void 0;\n return { value: o && o[i++], done: !o };\n }\n };\n throw new TypeError(s ? \"Object is not iterable.\" : \"Symbol.iterator is not defined.\");\n}\n\nexport function __read(o, n) {\n var m = typeof Symbol === \"function\" && o[Symbol.iterator];\n if (!m) return o;\n var i = m.call(o), r, ar = [], e;\n try {\n while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);\n }\n catch (error) { e = { error: error }; }\n finally {\n try {\n if (r && !r.done && (m = i[\"return\"])) m.call(i);\n }\n finally { if (e) throw e.error; }\n }\n return ar;\n}\n\n/** @deprecated */\nexport function __spread() {\n for (var ar = [], i = 0; i < arguments.length; i++)\n ar = ar.concat(__read(arguments[i]));\n return ar;\n}\n\n/** @deprecated */\nexport function __spreadArrays() {\n for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;\n for (var r = Array(s), k = 0, i = 0; i < il; i++)\n for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)\n r[k] = a[j];\n return r;\n}\n\nexport function __spreadArray(to, from, pack) {\n if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {\n if (ar || !(i in from)) {\n if (!ar) ar = Array.prototype.slice.call(from, 0, i);\n ar[i] = from[i];\n }\n }\n return to.concat(ar || Array.prototype.slice.call(from));\n}\n\nexport function __await(v) {\n return this instanceof __await ? (this.v = v, this) : new __await(v);\n}\n\nexport function __asyncGenerator(thisArg, _arguments, generator) {\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\n var g = generator.apply(thisArg, _arguments || []), i, q = [];\n return i = Object.create((typeof AsyncIterator === \"function\" ? AsyncIterator : Object).prototype), verb(\"next\"), verb(\"throw\"), verb(\"return\", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;\n function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }\n function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } }\n function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }\n function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }\n function fulfill(value) { resume(\"next\", value); }\n function reject(value) { resume(\"throw\", value); }\n function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }\n}\n\nexport function __asyncDelegator(o) {\n var i, p;\n return i = {}, verb(\"next\"), verb(\"throw\", function (e) { throw e; }), verb(\"return\"), i[Symbol.iterator] = function () { return this; }, i;\n function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: false } : f ? f(v) : v; } : f; }\n}\n\nexport function __asyncValues(o) {\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\n var m = o[Symbol.asyncIterator], i;\n return m ? m.call(o) : (o = typeof __values === \"function\" ? __values(o) : o[Symbol.iterator](), i = {}, verb(\"next\"), verb(\"throw\"), verb(\"return\"), i[Symbol.asyncIterator] = function () { return this; }, i);\n function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }\n function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }\n}\n\nexport function __makeTemplateObject(cooked, raw) {\n if (Object.defineProperty) { Object.defineProperty(cooked, \"raw\", { value: raw }); } else { cooked.raw = raw; }\n return cooked;\n};\n\nvar __setModuleDefault = Object.create ? (function(o, v) {\n Object.defineProperty(o, \"default\", { enumerable: true, value: v });\n}) : function(o, v) {\n o[\"default\"] = v;\n};\n\nexport function __importStar(mod) {\n if (mod && mod.__esModule) return mod;\n var result = {};\n if (mod != null) for (var k in mod) if (k !== \"default\" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);\n __setModuleDefault(result, mod);\n return result;\n}\n\nexport function __importDefault(mod) {\n return (mod && mod.__esModule) ? mod : { default: mod };\n}\n\nexport function __classPrivateFieldGet(receiver, state, kind, f) {\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a getter\");\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot read private member from an object whose class did not declare it\");\n return kind === \"m\" ? f : kind === \"a\" ? f.call(receiver) : f ? f.value : state.get(receiver);\n}\n\nexport function __classPrivateFieldSet(receiver, state, value, kind, f) {\n if (kind === \"m\") throw new TypeError(\"Private method is not writable\");\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a setter\");\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot write private member to an object whose class did not declare it\");\n return (kind === \"a\" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;\n}\n\nexport function __classPrivateFieldIn(state, receiver) {\n if (receiver === null || (typeof receiver !== \"object\" && typeof receiver !== \"function\")) throw new TypeError(\"Cannot use 'in' operator on non-object\");\n return typeof state === \"function\" ? receiver === state : state.has(receiver);\n}\n\nexport function __addDisposableResource(env, value, async) {\n if (value !== null && value !== void 0) {\n if (typeof value !== \"object\" && typeof value !== \"function\") throw new TypeError(\"Object expected.\");\n var dispose, inner;\n if (async) {\n if (!Symbol.asyncDispose) throw new TypeError(\"Symbol.asyncDispose is not defined.\");\n dispose = value[Symbol.asyncDispose];\n }\n if (dispose === void 0) {\n if (!Symbol.dispose) throw new TypeError(\"Symbol.dispose is not defined.\");\n dispose = value[Symbol.dispose];\n if (async) inner = dispose;\n }\n if (typeof dispose !== \"function\") throw new TypeError(\"Object not disposable.\");\n if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };\n env.stack.push({ value: value, dispose: dispose, async: async });\n }\n else if (async) {\n env.stack.push({ async: true });\n }\n return value;\n}\n\nvar _SuppressedError = typeof SuppressedError === \"function\" ? SuppressedError : function (error, suppressed, message) {\n var e = new Error(message);\n return e.name = \"SuppressedError\", e.error = error, e.suppressed = suppressed, e;\n};\n\nexport function __disposeResources(env) {\n function fail(e) {\n env.error = env.hasError ? new _SuppressedError(e, env.error, \"An error was suppressed during disposal.\") : e;\n env.hasError = true;\n }\n var r, s = 0;\n function next() {\n while (r = env.stack.pop()) {\n try {\n if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);\n if (r.dispose) {\n var result = r.dispose.call(r.value);\n if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });\n }\n else s |= 1;\n }\n catch (e) {\n fail(e);\n }\n }\n if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();\n if (env.hasError) throw env.error;\n }\n return next();\n}\n\nexport default {\n __extends,\n __assign,\n __rest,\n __decorate,\n __param,\n __metadata,\n __awaiter,\n __generator,\n __createBinding,\n __exportStar,\n __values,\n __read,\n __spread,\n __spreadArrays,\n __spreadArray,\n __await,\n __asyncGenerator,\n __asyncDelegator,\n __asyncValues,\n __makeTemplateObject,\n __importStar,\n __importDefault,\n __classPrivateFieldGet,\n __classPrivateFieldSet,\n __classPrivateFieldIn,\n __addDisposableResource,\n __disposeResources,\n};\n", "/**\n * Returns true if the object is a function.\n * @param value The value to check\n */\nexport function isFunction(value: any): value is (...args: any[]) => any {\n return typeof value === 'function';\n}\n", "/**\n * Used to create Error subclasses until the community moves away from ES5.\n *\n * This is because compiling from TypeScript down to ES5 has issues with subclassing Errors\n * as well as other built-in types: https://github.com/Microsoft/TypeScript/issues/12123\n *\n * @param createImpl A factory function to create the actual constructor implementation. The returned\n * function should be a named function that calls `_super` internally.\n */\nexport function createErrorClass(createImpl: (_super: any) => any): T {\n const _super = (instance: any) => {\n Error.call(instance);\n instance.stack = new Error().stack;\n };\n\n const ctorFunc = createImpl(_super);\n ctorFunc.prototype = Object.create(Error.prototype);\n ctorFunc.prototype.constructor = ctorFunc;\n return ctorFunc;\n}\n", "import { createErrorClass } from './createErrorClass';\n\nexport interface UnsubscriptionError extends Error {\n readonly errors: any[];\n}\n\nexport interface UnsubscriptionErrorCtor {\n /**\n * @deprecated Internal implementation detail. Do not construct error instances.\n * Cannot be tagged as internal: https://github.com/ReactiveX/rxjs/issues/6269\n */\n new (errors: any[]): UnsubscriptionError;\n}\n\n/**\n * An error thrown when one or more errors have occurred during the\n * `unsubscribe` of a {@link Subscription}.\n */\nexport const UnsubscriptionError: UnsubscriptionErrorCtor = createErrorClass(\n (_super) =>\n function UnsubscriptionErrorImpl(this: any, errors: (Error | string)[]) {\n _super(this);\n this.message = errors\n ? `${errors.length} errors occurred during unsubscription:\n${errors.map((err, i) => `${i + 1}) ${err.toString()}`).join('\\n ')}`\n : '';\n this.name = 'UnsubscriptionError';\n this.errors = errors;\n }\n);\n", "/**\n * Removes an item from an array, mutating it.\n * @param arr The array to remove the item from\n * @param item The item to remove\n */\nexport function arrRemove(arr: T[] | undefined | null, item: T) {\n if (arr) {\n const index = arr.indexOf(item);\n 0 <= index && arr.splice(index, 1);\n }\n}\n", "import { isFunction } from './util/isFunction';\nimport { UnsubscriptionError } from './util/UnsubscriptionError';\nimport { SubscriptionLike, TeardownLogic, Unsubscribable } from './types';\nimport { arrRemove } from './util/arrRemove';\n\n/**\n * Represents a disposable resource, such as the execution of an Observable. A\n * Subscription has one important method, `unsubscribe`, that takes no argument\n * and just disposes the resource held by the subscription.\n *\n * Additionally, subscriptions may be grouped together through the `add()`\n * method, which will attach a child Subscription to the current Subscription.\n * When a Subscription is unsubscribed, all its children (and its grandchildren)\n * will be unsubscribed as well.\n */\nexport class Subscription implements SubscriptionLike {\n public static EMPTY = (() => {\n const empty = new Subscription();\n empty.closed = true;\n return empty;\n })();\n\n /**\n * A flag to indicate whether this Subscription has already been unsubscribed.\n */\n public closed = false;\n\n private _parentage: Subscription[] | Subscription | null = null;\n\n /**\n * The list of registered finalizers to execute upon unsubscription. Adding and removing from this\n * list occurs in the {@link #add} and {@link #remove} methods.\n */\n private _finalizers: Exclude[] | null = null;\n\n /**\n * @param initialTeardown A function executed first as part of the finalization\n * process that is kicked off when {@link #unsubscribe} is called.\n */\n constructor(private initialTeardown?: () => void) {}\n\n /**\n * Disposes the resources held by the subscription. May, for instance, cancel\n * an ongoing Observable execution or cancel any other type of work that\n * started when the Subscription was created.\n */\n unsubscribe(): void {\n let errors: any[] | undefined;\n\n if (!this.closed) {\n this.closed = true;\n\n // Remove this from it's parents.\n const { _parentage } = this;\n if (_parentage) {\n this._parentage = null;\n if (Array.isArray(_parentage)) {\n for (const parent of _parentage) {\n parent.remove(this);\n }\n } else {\n _parentage.remove(this);\n }\n }\n\n const { initialTeardown: initialFinalizer } = this;\n if (isFunction(initialFinalizer)) {\n try {\n initialFinalizer();\n } catch (e) {\n errors = e instanceof UnsubscriptionError ? e.errors : [e];\n }\n }\n\n const { _finalizers } = this;\n if (_finalizers) {\n this._finalizers = null;\n for (const finalizer of _finalizers) {\n try {\n execFinalizer(finalizer);\n } catch (err) {\n errors = errors ?? [];\n if (err instanceof UnsubscriptionError) {\n errors = [...errors, ...err.errors];\n } else {\n errors.push(err);\n }\n }\n }\n }\n\n if (errors) {\n throw new UnsubscriptionError(errors);\n }\n }\n }\n\n /**\n * Adds a finalizer to this subscription, so that finalization will be unsubscribed/called\n * when this subscription is unsubscribed. If this subscription is already {@link #closed},\n * because it has already been unsubscribed, then whatever finalizer is passed to it\n * will automatically be executed (unless the finalizer itself is also a closed subscription).\n *\n * Closed Subscriptions cannot be added as finalizers to any subscription. Adding a closed\n * subscription to a any subscription will result in no operation. (A noop).\n *\n * Adding a subscription to itself, or adding `null` or `undefined` will not perform any\n * operation at all. (A noop).\n *\n * `Subscription` instances that are added to this instance will automatically remove themselves\n * if they are unsubscribed. Functions and {@link Unsubscribable} objects that you wish to remove\n * will need to be removed manually with {@link #remove}\n *\n * @param teardown The finalization logic to add to this subscription.\n */\n add(teardown: TeardownLogic): void {\n // Only add the finalizer if it's not undefined\n // and don't add a subscription to itself.\n if (teardown && teardown !== this) {\n if (this.closed) {\n // If this subscription is already closed,\n // execute whatever finalizer is handed to it automatically.\n execFinalizer(teardown);\n } else {\n if (teardown instanceof Subscription) {\n // We don't add closed subscriptions, and we don't add the same subscription\n // twice. Subscription unsubscribe is idempotent.\n if (teardown.closed || teardown._hasParent(this)) {\n return;\n }\n teardown._addParent(this);\n }\n (this._finalizers = this._finalizers ?? []).push(teardown);\n }\n }\n }\n\n /**\n * Checks to see if a this subscription already has a particular parent.\n * This will signal that this subscription has already been added to the parent in question.\n * @param parent the parent to check for\n */\n private _hasParent(parent: Subscription) {\n const { _parentage } = this;\n return _parentage === parent || (Array.isArray(_parentage) && _parentage.includes(parent));\n }\n\n /**\n * Adds a parent to this subscription so it can be removed from the parent if it\n * unsubscribes on it's own.\n *\n * NOTE: THIS ASSUMES THAT {@link _hasParent} HAS ALREADY BEEN CHECKED.\n * @param parent The parent subscription to add\n */\n private _addParent(parent: Subscription) {\n const { _parentage } = this;\n this._parentage = Array.isArray(_parentage) ? (_parentage.push(parent), _parentage) : _parentage ? [_parentage, parent] : parent;\n }\n\n /**\n * Called on a child when it is removed via {@link #remove}.\n * @param parent The parent to remove\n */\n private _removeParent(parent: Subscription) {\n const { _parentage } = this;\n if (_parentage === parent) {\n this._parentage = null;\n } else if (Array.isArray(_parentage)) {\n arrRemove(_parentage, parent);\n }\n }\n\n /**\n * Removes a finalizer from this subscription that was previously added with the {@link #add} method.\n *\n * Note that `Subscription` instances, when unsubscribed, will automatically remove themselves\n * from every other `Subscription` they have been added to. This means that using the `remove` method\n * is not a common thing and should be used thoughtfully.\n *\n * If you add the same finalizer instance of a function or an unsubscribable object to a `Subscription` instance\n * more than once, you will need to call `remove` the same number of times to remove all instances.\n *\n * All finalizer instances are removed to free up memory upon unsubscription.\n *\n * @param teardown The finalizer to remove from this subscription\n */\n remove(teardown: Exclude): void {\n const { _finalizers } = this;\n _finalizers && arrRemove(_finalizers, teardown);\n\n if (teardown instanceof Subscription) {\n teardown._removeParent(this);\n }\n }\n}\n\nexport const EMPTY_SUBSCRIPTION = Subscription.EMPTY;\n\nexport function isSubscription(value: any): value is Subscription {\n return (\n value instanceof Subscription ||\n (value && 'closed' in value && isFunction(value.remove) && isFunction(value.add) && isFunction(value.unsubscribe))\n );\n}\n\nfunction execFinalizer(finalizer: Unsubscribable | (() => void)) {\n if (isFunction(finalizer)) {\n finalizer();\n } else {\n finalizer.unsubscribe();\n }\n}\n", "import { Subscriber } from './Subscriber';\nimport { ObservableNotification } from './types';\n\n/**\n * The {@link GlobalConfig} object for RxJS. It is used to configure things\n * like how to react on unhandled errors.\n */\nexport const config: GlobalConfig = {\n onUnhandledError: null,\n onStoppedNotification: null,\n Promise: undefined,\n useDeprecatedSynchronousErrorHandling: false,\n useDeprecatedNextContext: false,\n};\n\n/**\n * The global configuration object for RxJS, used to configure things\n * like how to react on unhandled errors. Accessible via {@link config}\n * object.\n */\nexport interface GlobalConfig {\n /**\n * A registration point for unhandled errors from RxJS. These are errors that\n * cannot were not handled by consuming code in the usual subscription path. For\n * example, if you have this configured, and you subscribe to an observable without\n * providing an error handler, errors from that subscription will end up here. This\n * will _always_ be called asynchronously on another job in the runtime. This is because\n * we do not want errors thrown in this user-configured handler to interfere with the\n * behavior of the library.\n */\n onUnhandledError: ((err: any) => void) | null;\n\n /**\n * A registration point for notifications that cannot be sent to subscribers because they\n * have completed, errored or have been explicitly unsubscribed. By default, next, complete\n * and error notifications sent to stopped subscribers are noops. However, sometimes callers\n * might want a different behavior. For example, with sources that attempt to report errors\n * to stopped subscribers, a caller can configure RxJS to throw an unhandled error instead.\n * This will _always_ be called asynchronously on another job in the runtime. This is because\n * we do not want errors thrown in this user-configured handler to interfere with the\n * behavior of the library.\n */\n onStoppedNotification: ((notification: ObservableNotification, subscriber: Subscriber) => void) | null;\n\n /**\n * The promise constructor used by default for {@link Observable#toPromise toPromise} and {@link Observable#forEach forEach}\n * methods.\n *\n * @deprecated As of version 8, RxJS will no longer support this sort of injection of a\n * Promise constructor. If you need a Promise implementation other than native promises,\n * please polyfill/patch Promise as you see appropriate. Will be removed in v8.\n */\n Promise?: PromiseConstructorLike;\n\n /**\n * If true, turns on synchronous error rethrowing, which is a deprecated behavior\n * in v6 and higher. This behavior enables bad patterns like wrapping a subscribe\n * call in a try/catch block. It also enables producer interference, a nasty bug\n * where a multicast can be broken for all observers by a downstream consumer with\n * an unhandled error. DO NOT USE THIS FLAG UNLESS IT'S NEEDED TO BUY TIME\n * FOR MIGRATION REASONS.\n *\n * @deprecated As of version 8, RxJS will no longer support synchronous throwing\n * of unhandled errors. All errors will be thrown on a separate call stack to prevent bad\n * behaviors described above. Will be removed in v8.\n */\n useDeprecatedSynchronousErrorHandling: boolean;\n\n /**\n * If true, enables an as-of-yet undocumented feature from v5: The ability to access\n * `unsubscribe()` via `this` context in `next` functions created in observers passed\n * to `subscribe`.\n *\n * This is being removed because the performance was severely problematic, and it could also cause\n * issues when types other than POJOs are passed to subscribe as subscribers, as they will likely have\n * their `this` context overwritten.\n *\n * @deprecated As of version 8, RxJS will no longer support altering the\n * context of next functions provided as part of an observer to Subscribe. Instead,\n * you will have access to a subscription or a signal or token that will allow you to do things like\n * unsubscribe and test closed status. Will be removed in v8.\n */\n useDeprecatedNextContext: boolean;\n}\n", "import type { TimerHandle } from './timerHandle';\ntype SetTimeoutFunction = (handler: () => void, timeout?: number, ...args: any[]) => TimerHandle;\ntype ClearTimeoutFunction = (handle: TimerHandle) => void;\n\ninterface TimeoutProvider {\n setTimeout: SetTimeoutFunction;\n clearTimeout: ClearTimeoutFunction;\n delegate:\n | {\n setTimeout: SetTimeoutFunction;\n clearTimeout: ClearTimeoutFunction;\n }\n | undefined;\n}\n\nexport const timeoutProvider: TimeoutProvider = {\n // When accessing the delegate, use the variable rather than `this` so that\n // the functions can be called without being bound to the provider.\n setTimeout(handler: () => void, timeout?: number, ...args) {\n const { delegate } = timeoutProvider;\n if (delegate?.setTimeout) {\n return delegate.setTimeout(handler, timeout, ...args);\n }\n return setTimeout(handler, timeout, ...args);\n },\n clearTimeout(handle) {\n const { delegate } = timeoutProvider;\n return (delegate?.clearTimeout || clearTimeout)(handle as any);\n },\n delegate: undefined,\n};\n", "import { config } from '../config';\nimport { timeoutProvider } from '../scheduler/timeoutProvider';\n\n/**\n * Handles an error on another job either with the user-configured {@link onUnhandledError},\n * or by throwing it on that new job so it can be picked up by `window.onerror`, `process.on('error')`, etc.\n *\n * This should be called whenever there is an error that is out-of-band with the subscription\n * or when an error hits a terminal boundary of the subscription and no error handler was provided.\n *\n * @param err the error to report\n */\nexport function reportUnhandledError(err: any) {\n timeoutProvider.setTimeout(() => {\n const { onUnhandledError } = config;\n if (onUnhandledError) {\n // Execute the user-configured error handler.\n onUnhandledError(err);\n } else {\n // Throw so it is picked up by the runtime's uncaught error mechanism.\n throw err;\n }\n });\n}\n", "/* tslint:disable:no-empty */\nexport function noop() { }\n", "import { CompleteNotification, NextNotification, ErrorNotification } from './types';\n\n/**\n * A completion object optimized for memory use and created to be the\n * same \"shape\" as other notifications in v8.\n * @internal\n */\nexport const COMPLETE_NOTIFICATION = (() => createNotification('C', undefined, undefined) as CompleteNotification)();\n\n/**\n * Internal use only. Creates an optimized error notification that is the same \"shape\"\n * as other notifications.\n * @internal\n */\nexport function errorNotification(error: any): ErrorNotification {\n return createNotification('E', undefined, error) as any;\n}\n\n/**\n * Internal use only. Creates an optimized next notification that is the same \"shape\"\n * as other notifications.\n * @internal\n */\nexport function nextNotification(value: T) {\n return createNotification('N', value, undefined) as NextNotification;\n}\n\n/**\n * Ensures that all notifications created internally have the same \"shape\" in v8.\n *\n * TODO: This is only exported to support a crazy legacy test in `groupBy`.\n * @internal\n */\nexport function createNotification(kind: 'N' | 'E' | 'C', value: any, error: any) {\n return {\n kind,\n value,\n error,\n };\n}\n", "import { config } from '../config';\n\nlet context: { errorThrown: boolean; error: any } | null = null;\n\n/**\n * Handles dealing with errors for super-gross mode. Creates a context, in which\n * any synchronously thrown errors will be passed to {@link captureError}. Which\n * will record the error such that it will be rethrown after the call back is complete.\n * TODO: Remove in v8\n * @param cb An immediately executed function.\n */\nexport function errorContext(cb: () => void) {\n if (config.useDeprecatedSynchronousErrorHandling) {\n const isRoot = !context;\n if (isRoot) {\n context = { errorThrown: false, error: null };\n }\n cb();\n if (isRoot) {\n const { errorThrown, error } = context!;\n context = null;\n if (errorThrown) {\n throw error;\n }\n }\n } else {\n // This is the general non-deprecated path for everyone that\n // isn't crazy enough to use super-gross mode (useDeprecatedSynchronousErrorHandling)\n cb();\n }\n}\n\n/**\n * Captures errors only in super-gross mode.\n * @param err the error to capture\n */\nexport function captureError(err: any) {\n if (config.useDeprecatedSynchronousErrorHandling && context) {\n context.errorThrown = true;\n context.error = err;\n }\n}\n", "import { isFunction } from './util/isFunction';\nimport { Observer, ObservableNotification } from './types';\nimport { isSubscription, Subscription } from './Subscription';\nimport { config } from './config';\nimport { reportUnhandledError } from './util/reportUnhandledError';\nimport { noop } from './util/noop';\nimport { nextNotification, errorNotification, COMPLETE_NOTIFICATION } from './NotificationFactories';\nimport { timeoutProvider } from './scheduler/timeoutProvider';\nimport { captureError } from './util/errorContext';\n\n/**\n * Implements the {@link Observer} interface and extends the\n * {@link Subscription} class. While the {@link Observer} is the public API for\n * consuming the values of an {@link Observable}, all Observers get converted to\n * a Subscriber, in order to provide Subscription-like capabilities such as\n * `unsubscribe`. Subscriber is a common type in RxJS, and crucial for\n * implementing operators, but it is rarely used as a public API.\n */\nexport class Subscriber extends Subscription implements Observer {\n /**\n * A static factory for a Subscriber, given a (potentially partial) definition\n * of an Observer.\n * @param next The `next` callback of an Observer.\n * @param error The `error` callback of an\n * Observer.\n * @param complete The `complete` callback of an\n * Observer.\n * @return A Subscriber wrapping the (partially defined)\n * Observer represented by the given arguments.\n * @deprecated Do not use. Will be removed in v8. There is no replacement for this\n * method, and there is no reason to be creating instances of `Subscriber` directly.\n * If you have a specific use case, please file an issue.\n */\n static create(next?: (x?: T) => void, error?: (e?: any) => void, complete?: () => void): Subscriber {\n return new SafeSubscriber(next, error, complete);\n }\n\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n protected isStopped: boolean = false;\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n protected destination: Subscriber | Observer; // this `any` is the escape hatch to erase extra type param (e.g. R)\n\n /**\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n * There is no reason to directly create an instance of Subscriber. This type is exported for typings reasons.\n */\n constructor(destination?: Subscriber | Observer) {\n super();\n if (destination) {\n this.destination = destination;\n // Automatically chain subscriptions together here.\n // if destination is a Subscription, then it is a Subscriber.\n if (isSubscription(destination)) {\n destination.add(this);\n }\n } else {\n this.destination = EMPTY_OBSERVER;\n }\n }\n\n /**\n * The {@link Observer} callback to receive notifications of type `next` from\n * the Observable, with a value. The Observable may call this method 0 or more\n * times.\n * @param value The `next` value.\n */\n next(value: T): void {\n if (this.isStopped) {\n handleStoppedNotification(nextNotification(value), this);\n } else {\n this._next(value!);\n }\n }\n\n /**\n * The {@link Observer} callback to receive notifications of type `error` from\n * the Observable, with an attached `Error`. Notifies the Observer that\n * the Observable has experienced an error condition.\n * @param err The `error` exception.\n */\n error(err?: any): void {\n if (this.isStopped) {\n handleStoppedNotification(errorNotification(err), this);\n } else {\n this.isStopped = true;\n this._error(err);\n }\n }\n\n /**\n * The {@link Observer} callback to receive a valueless notification of type\n * `complete` from the Observable. Notifies the Observer that the Observable\n * has finished sending push-based notifications.\n */\n complete(): void {\n if (this.isStopped) {\n handleStoppedNotification(COMPLETE_NOTIFICATION, this);\n } else {\n this.isStopped = true;\n this._complete();\n }\n }\n\n unsubscribe(): void {\n if (!this.closed) {\n this.isStopped = true;\n super.unsubscribe();\n this.destination = null!;\n }\n }\n\n protected _next(value: T): void {\n this.destination.next(value);\n }\n\n protected _error(err: any): void {\n try {\n this.destination.error(err);\n } finally {\n this.unsubscribe();\n }\n }\n\n protected _complete(): void {\n try {\n this.destination.complete();\n } finally {\n this.unsubscribe();\n }\n }\n}\n\n/**\n * This bind is captured here because we want to be able to have\n * compatibility with monoid libraries that tend to use a method named\n * `bind`. In particular, a library called Monio requires this.\n */\nconst _bind = Function.prototype.bind;\n\nfunction bind any>(fn: Fn, thisArg: any): Fn {\n return _bind.call(fn, thisArg);\n}\n\n/**\n * Internal optimization only, DO NOT EXPOSE.\n * @internal\n */\nclass ConsumerObserver implements Observer {\n constructor(private partialObserver: Partial>) {}\n\n next(value: T): void {\n const { partialObserver } = this;\n if (partialObserver.next) {\n try {\n partialObserver.next(value);\n } catch (error) {\n handleUnhandledError(error);\n }\n }\n }\n\n error(err: any): void {\n const { partialObserver } = this;\n if (partialObserver.error) {\n try {\n partialObserver.error(err);\n } catch (error) {\n handleUnhandledError(error);\n }\n } else {\n handleUnhandledError(err);\n }\n }\n\n complete(): void {\n const { partialObserver } = this;\n if (partialObserver.complete) {\n try {\n partialObserver.complete();\n } catch (error) {\n handleUnhandledError(error);\n }\n }\n }\n}\n\nexport class SafeSubscriber extends Subscriber {\n constructor(\n observerOrNext?: Partial> | ((value: T) => void) | null,\n error?: ((e?: any) => void) | null,\n complete?: (() => void) | null\n ) {\n super();\n\n let partialObserver: Partial>;\n if (isFunction(observerOrNext) || !observerOrNext) {\n // The first argument is a function, not an observer. The next\n // two arguments *could* be observers, or they could be empty.\n partialObserver = {\n next: (observerOrNext ?? undefined) as ((value: T) => void) | undefined,\n error: error ?? undefined,\n complete: complete ?? undefined,\n };\n } else {\n // The first argument is a partial observer.\n let context: any;\n if (this && config.useDeprecatedNextContext) {\n // This is a deprecated path that made `this.unsubscribe()` available in\n // next handler functions passed to subscribe. This only exists behind a flag\n // now, as it is *very* slow.\n context = Object.create(observerOrNext);\n context.unsubscribe = () => this.unsubscribe();\n partialObserver = {\n next: observerOrNext.next && bind(observerOrNext.next, context),\n error: observerOrNext.error && bind(observerOrNext.error, context),\n complete: observerOrNext.complete && bind(observerOrNext.complete, context),\n };\n } else {\n // The \"normal\" path. Just use the partial observer directly.\n partialObserver = observerOrNext;\n }\n }\n\n // Wrap the partial observer to ensure it's a full observer, and\n // make sure proper error handling is accounted for.\n this.destination = new ConsumerObserver(partialObserver);\n }\n}\n\nfunction handleUnhandledError(error: any) {\n if (config.useDeprecatedSynchronousErrorHandling) {\n captureError(error);\n } else {\n // Ideal path, we report this as an unhandled error,\n // which is thrown on a new call stack.\n reportUnhandledError(error);\n }\n}\n\n/**\n * An error handler used when no error handler was supplied\n * to the SafeSubscriber -- meaning no error handler was supplied\n * do the `subscribe` call on our observable.\n * @param err The error to handle\n */\nfunction defaultErrorHandler(err: any) {\n throw err;\n}\n\n/**\n * A handler for notifications that cannot be sent to a stopped subscriber.\n * @param notification The notification being sent.\n * @param subscriber The stopped subscriber.\n */\nfunction handleStoppedNotification(notification: ObservableNotification, subscriber: Subscriber) {\n const { onStoppedNotification } = config;\n onStoppedNotification && timeoutProvider.setTimeout(() => onStoppedNotification(notification, subscriber));\n}\n\n/**\n * The observer used as a stub for subscriptions where the user did not\n * pass any arguments to `subscribe`. Comes with the default error handling\n * behavior.\n */\nexport const EMPTY_OBSERVER: Readonly> & { closed: true } = {\n closed: true,\n next: noop,\n error: defaultErrorHandler,\n complete: noop,\n};\n", "/**\n * Symbol.observable or a string \"@@observable\". Used for interop\n *\n * @deprecated We will no longer be exporting this symbol in upcoming versions of RxJS.\n * Instead polyfill and use Symbol.observable directly *or* use https://www.npmjs.com/package/symbol-observable\n */\nexport const observable: string | symbol = (() => (typeof Symbol === 'function' && Symbol.observable) || '@@observable')();\n", "/**\n * This function takes one parameter and just returns it. Simply put,\n * this is like `(x: T): T => x`.\n *\n * ## Examples\n *\n * This is useful in some cases when using things like `mergeMap`\n *\n * ```ts\n * import { interval, take, map, range, mergeMap, identity } from 'rxjs';\n *\n * const source$ = interval(1000).pipe(take(5));\n *\n * const result$ = source$.pipe(\n * map(i => range(i)),\n * mergeMap(identity) // same as mergeMap(x => x)\n * );\n *\n * result$.subscribe({\n * next: console.log\n * });\n * ```\n *\n * Or when you want to selectively apply an operator\n *\n * ```ts\n * import { interval, take, identity } from 'rxjs';\n *\n * const shouldLimit = () => Math.random() < 0.5;\n *\n * const source$ = interval(1000);\n *\n * const result$ = source$.pipe(shouldLimit() ? take(5) : identity);\n *\n * result$.subscribe({\n * next: console.log\n * });\n * ```\n *\n * @param x Any value that is returned by this function\n * @returns The value passed as the first parameter to this function\n */\nexport function identity(x: T): T {\n return x;\n}\n", "import { identity } from './identity';\nimport { UnaryFunction } from '../types';\n\nexport function pipe(): typeof identity;\nexport function pipe(fn1: UnaryFunction): UnaryFunction;\nexport function pipe(fn1: UnaryFunction, fn2: UnaryFunction): UnaryFunction;\nexport function pipe(fn1: UnaryFunction, fn2: UnaryFunction, fn3: UnaryFunction): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction,\n fn8: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction,\n fn8: UnaryFunction,\n fn9: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction,\n fn8: UnaryFunction,\n fn9: UnaryFunction,\n ...fns: UnaryFunction[]\n): UnaryFunction;\n\n/**\n * pipe() can be called on one or more functions, each of which can take one argument (\"UnaryFunction\")\n * and uses it to return a value.\n * It returns a function that takes one argument, passes it to the first UnaryFunction, and then\n * passes the result to the next one, passes that result to the next one, and so on. \n */\nexport function pipe(...fns: Array>): UnaryFunction {\n return pipeFromArray(fns);\n}\n\n/** @internal */\nexport function pipeFromArray(fns: Array>): UnaryFunction {\n if (fns.length === 0) {\n return identity as UnaryFunction;\n }\n\n if (fns.length === 1) {\n return fns[0];\n }\n\n return function piped(input: T): R {\n return fns.reduce((prev: any, fn: UnaryFunction) => fn(prev), input as any);\n };\n}\n", "import { Operator } from './Operator';\nimport { SafeSubscriber, Subscriber } from './Subscriber';\nimport { isSubscription, Subscription } from './Subscription';\nimport { TeardownLogic, OperatorFunction, Subscribable, Observer } from './types';\nimport { observable as Symbol_observable } from './symbol/observable';\nimport { pipeFromArray } from './util/pipe';\nimport { config } from './config';\nimport { isFunction } from './util/isFunction';\nimport { errorContext } from './util/errorContext';\n\n/**\n * A representation of any set of values over any amount of time. This is the most basic building block\n * of RxJS.\n */\nexport class Observable implements Subscribable {\n /**\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n */\n source: Observable | undefined;\n\n /**\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n */\n operator: Operator | undefined;\n\n /**\n * @param subscribe The function that is called when the Observable is\n * initially subscribed to. This function is given a Subscriber, to which new values\n * can be `next`ed, or an `error` method can be called to raise an error, or\n * `complete` can be called to notify of a successful completion.\n */\n constructor(subscribe?: (this: Observable, subscriber: Subscriber) => TeardownLogic) {\n if (subscribe) {\n this._subscribe = subscribe;\n }\n }\n\n // HACK: Since TypeScript inherits static properties too, we have to\n // fight against TypeScript here so Subject can have a different static create signature\n /**\n * Creates a new Observable by calling the Observable constructor\n * @param subscribe the subscriber function to be passed to the Observable constructor\n * @return A new observable.\n * @deprecated Use `new Observable()` instead. Will be removed in v8.\n */\n static create: (...args: any[]) => any = (subscribe?: (subscriber: Subscriber) => TeardownLogic) => {\n return new Observable(subscribe);\n };\n\n /**\n * Creates a new Observable, with this Observable instance as the source, and the passed\n * operator defined as the new observable's operator.\n * @param operator the operator defining the operation to take on the observable\n * @return A new observable with the Operator applied.\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n * If you have implemented an operator using `lift`, it is recommended that you create an\n * operator by simply returning `new Observable()` directly. See \"Creating new operators from\n * scratch\" section here: https://rxjs.dev/guide/operators\n */\n lift(operator?: Operator): Observable {\n const observable = new Observable();\n observable.source = this;\n observable.operator = operator;\n return observable;\n }\n\n subscribe(observerOrNext?: Partial> | ((value: T) => void)): Subscription;\n /** @deprecated Instead of passing separate callback arguments, use an observer argument. Signatures taking separate callback arguments will be removed in v8. Details: https://rxjs.dev/deprecations/subscribe-arguments */\n subscribe(next?: ((value: T) => void) | null, error?: ((error: any) => void) | null, complete?: (() => void) | null): Subscription;\n /**\n * Invokes an execution of an Observable and registers Observer handlers for notifications it will emit.\n *\n * Use it when you have all these Observables, but still nothing is happening.\n *\n * `subscribe` is not a regular operator, but a method that calls Observable's internal `subscribe` function. It\n * might be for example a function that you passed to Observable's constructor, but most of the time it is\n * a library implementation, which defines what will be emitted by an Observable, and when it be will emitted. This means\n * that calling `subscribe` is actually the moment when Observable starts its work, not when it is created, as it is often\n * the thought.\n *\n * Apart from starting the execution of an Observable, this method allows you to listen for values\n * that an Observable emits, as well as for when it completes or errors. You can achieve this in two\n * of the following ways.\n *\n * The first way is creating an object that implements {@link Observer} interface. It should have methods\n * defined by that interface, but note that it should be just a regular JavaScript object, which you can create\n * yourself in any way you want (ES6 class, classic function constructor, object literal etc.). In particular, do\n * not attempt to use any RxJS implementation details to create Observers - you don't need them. Remember also\n * that your object does not have to implement all methods. If you find yourself creating a method that doesn't\n * do anything, you can simply omit it. Note however, if the `error` method is not provided and an error happens,\n * it will be thrown asynchronously. Errors thrown asynchronously cannot be caught using `try`/`catch`. Instead,\n * use the {@link onUnhandledError} configuration option or use a runtime handler (like `window.onerror` or\n * `process.on('error)`) to be notified of unhandled errors. Because of this, it's recommended that you provide\n * an `error` method to avoid missing thrown errors.\n *\n * The second way is to give up on Observer object altogether and simply provide callback functions in place of its methods.\n * This means you can provide three functions as arguments to `subscribe`, where the first function is equivalent\n * of a `next` method, the second of an `error` method and the third of a `complete` method. Just as in case of an Observer,\n * if you do not need to listen for something, you can omit a function by passing `undefined` or `null`,\n * since `subscribe` recognizes these functions by where they were placed in function call. When it comes\n * to the `error` function, as with an Observer, if not provided, errors emitted by an Observable will be thrown asynchronously.\n *\n * You can, however, subscribe with no parameters at all. This may be the case where you're not interested in terminal events\n * and you also handled emissions internally by using operators (e.g. using `tap`).\n *\n * Whichever style of calling `subscribe` you use, in both cases it returns a Subscription object.\n * This object allows you to call `unsubscribe` on it, which in turn will stop the work that an Observable does and will clean\n * up all resources that an Observable used. Note that cancelling a subscription will not call `complete` callback\n * provided to `subscribe` function, which is reserved for a regular completion signal that comes from an Observable.\n *\n * Remember that callbacks provided to `subscribe` are not guaranteed to be called asynchronously.\n * It is an Observable itself that decides when these functions will be called. For example {@link of}\n * by default emits all its values synchronously. Always check documentation for how given Observable\n * will behave when subscribed and if its default behavior can be modified with a `scheduler`.\n *\n * #### Examples\n *\n * Subscribe with an {@link guide/observer Observer}\n *\n * ```ts\n * import { of } from 'rxjs';\n *\n * const sumObserver = {\n * sum: 0,\n * next(value) {\n * console.log('Adding: ' + value);\n * this.sum = this.sum + value;\n * },\n * error() {\n * // We actually could just remove this method,\n * // since we do not really care about errors right now.\n * },\n * complete() {\n * console.log('Sum equals: ' + this.sum);\n * }\n * };\n *\n * of(1, 2, 3) // Synchronously emits 1, 2, 3 and then completes.\n * .subscribe(sumObserver);\n *\n * // Logs:\n * // 'Adding: 1'\n * // 'Adding: 2'\n * // 'Adding: 3'\n * // 'Sum equals: 6'\n * ```\n *\n * Subscribe with functions ({@link deprecations/subscribe-arguments deprecated})\n *\n * ```ts\n * import { of } from 'rxjs'\n *\n * let sum = 0;\n *\n * of(1, 2, 3).subscribe(\n * value => {\n * console.log('Adding: ' + value);\n * sum = sum + value;\n * },\n * undefined,\n * () => console.log('Sum equals: ' + sum)\n * );\n *\n * // Logs:\n * // 'Adding: 1'\n * // 'Adding: 2'\n * // 'Adding: 3'\n * // 'Sum equals: 6'\n * ```\n *\n * Cancel a subscription\n *\n * ```ts\n * import { interval } from 'rxjs';\n *\n * const subscription = interval(1000).subscribe({\n * next(num) {\n * console.log(num)\n * },\n * complete() {\n * // Will not be called, even when cancelling subscription.\n * console.log('completed!');\n * }\n * });\n *\n * setTimeout(() => {\n * subscription.unsubscribe();\n * console.log('unsubscribed!');\n * }, 2500);\n *\n * // Logs:\n * // 0 after 1s\n * // 1 after 2s\n * // 'unsubscribed!' after 2.5s\n * ```\n *\n * @param observerOrNext Either an {@link Observer} with some or all callback methods,\n * or the `next` handler that is called for each value emitted from the subscribed Observable.\n * @param error A handler for a terminal event resulting from an error. If no error handler is provided,\n * the error will be thrown asynchronously as unhandled.\n * @param complete A handler for a terminal event resulting from successful completion.\n * @return A subscription reference to the registered handlers.\n */\n subscribe(\n observerOrNext?: Partial> | ((value: T) => void) | null,\n error?: ((error: any) => void) | null,\n complete?: (() => void) | null\n ): Subscription {\n const subscriber = isSubscriber(observerOrNext) ? observerOrNext : new SafeSubscriber(observerOrNext, error, complete);\n\n errorContext(() => {\n const { operator, source } = this;\n subscriber.add(\n operator\n ? // We're dealing with a subscription in the\n // operator chain to one of our lifted operators.\n operator.call(subscriber, source)\n : source\n ? // If `source` has a value, but `operator` does not, something that\n // had intimate knowledge of our API, like our `Subject`, must have\n // set it. We're going to just call `_subscribe` directly.\n this._subscribe(subscriber)\n : // In all other cases, we're likely wrapping a user-provided initializer\n // function, so we need to catch errors and handle them appropriately.\n this._trySubscribe(subscriber)\n );\n });\n\n return subscriber;\n }\n\n /** @internal */\n protected _trySubscribe(sink: Subscriber): TeardownLogic {\n try {\n return this._subscribe(sink);\n } catch (err) {\n // We don't need to return anything in this case,\n // because it's just going to try to `add()` to a subscription\n // above.\n sink.error(err);\n }\n }\n\n /**\n * Used as a NON-CANCELLABLE means of subscribing to an observable, for use with\n * APIs that expect promises, like `async/await`. You cannot unsubscribe from this.\n *\n * **WARNING**: Only use this with observables you *know* will complete. If the source\n * observable does not complete, you will end up with a promise that is hung up, and\n * potentially all of the state of an async function hanging out in memory. To avoid\n * this situation, look into adding something like {@link timeout}, {@link take},\n * {@link takeWhile}, or {@link takeUntil} amongst others.\n *\n * #### Example\n *\n * ```ts\n * import { interval, take } from 'rxjs';\n *\n * const source$ = interval(1000).pipe(take(4));\n *\n * async function getTotal() {\n * let total = 0;\n *\n * await source$.forEach(value => {\n * total += value;\n * console.log('observable -> ' + value);\n * });\n *\n * return total;\n * }\n *\n * getTotal().then(\n * total => console.log('Total: ' + total)\n * );\n *\n * // Expected:\n * // 'observable -> 0'\n * // 'observable -> 1'\n * // 'observable -> 2'\n * // 'observable -> 3'\n * // 'Total: 6'\n * ```\n *\n * @param next A handler for each value emitted by the observable.\n * @return A promise that either resolves on observable completion or\n * rejects with the handled error.\n */\n forEach(next: (value: T) => void): Promise;\n\n /**\n * @param next a handler for each value emitted by the observable\n * @param promiseCtor a constructor function used to instantiate the Promise\n * @return a promise that either resolves on observable completion or\n * rejects with the handled error\n * @deprecated Passing a Promise constructor will no longer be available\n * in upcoming versions of RxJS. This is because it adds weight to the library, for very\n * little benefit. If you need this functionality, it is recommended that you either\n * polyfill Promise, or you create an adapter to convert the returned native promise\n * to whatever promise implementation you wanted. Will be removed in v8.\n */\n forEach(next: (value: T) => void, promiseCtor: PromiseConstructorLike): Promise;\n\n forEach(next: (value: T) => void, promiseCtor?: PromiseConstructorLike): Promise {\n promiseCtor = getPromiseCtor(promiseCtor);\n\n return new promiseCtor((resolve, reject) => {\n const subscriber = new SafeSubscriber({\n next: (value) => {\n try {\n next(value);\n } catch (err) {\n reject(err);\n subscriber.unsubscribe();\n }\n },\n error: reject,\n complete: resolve,\n });\n this.subscribe(subscriber);\n }) as Promise;\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): TeardownLogic {\n return this.source?.subscribe(subscriber);\n }\n\n /**\n * An interop point defined by the es7-observable spec https://github.com/zenparsing/es-observable\n * @return This instance of the observable.\n */\n [Symbol_observable]() {\n return this;\n }\n\n /* tslint:disable:max-line-length */\n pipe(): Observable;\n pipe(op1: OperatorFunction): Observable;\n pipe(op1: OperatorFunction, op2: OperatorFunction): Observable;\n pipe(op1: OperatorFunction, op2: OperatorFunction, op3: OperatorFunction): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction,\n op8: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction,\n op8: OperatorFunction,\n op9: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction,\n op8: OperatorFunction,\n op9: OperatorFunction,\n ...operations: OperatorFunction[]\n ): Observable;\n /* tslint:enable:max-line-length */\n\n /**\n * Used to stitch together functional operators into a chain.\n *\n * ## Example\n *\n * ```ts\n * import { interval, filter, map, scan } from 'rxjs';\n *\n * interval(1000)\n * .pipe(\n * filter(x => x % 2 === 0),\n * map(x => x + x),\n * scan((acc, x) => acc + x)\n * )\n * .subscribe(x => console.log(x));\n * ```\n *\n * @return The Observable result of all the operators having been called\n * in the order they were passed in.\n */\n pipe(...operations: OperatorFunction[]): Observable {\n return pipeFromArray(operations)(this);\n }\n\n /* tslint:disable:max-line-length */\n /** @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise */\n toPromise(): Promise;\n /** @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise */\n toPromise(PromiseCtor: typeof Promise): Promise;\n /** @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise */\n toPromise(PromiseCtor: PromiseConstructorLike): Promise;\n /* tslint:enable:max-line-length */\n\n /**\n * Subscribe to this Observable and get a Promise resolving on\n * `complete` with the last emission (if any).\n *\n * **WARNING**: Only use this with observables you *know* will complete. If the source\n * observable does not complete, you will end up with a promise that is hung up, and\n * potentially all of the state of an async function hanging out in memory. To avoid\n * this situation, look into adding something like {@link timeout}, {@link take},\n * {@link takeWhile}, or {@link takeUntil} amongst others.\n *\n * @param [promiseCtor] a constructor function used to instantiate\n * the Promise\n * @return A Promise that resolves with the last value emit, or\n * rejects on an error. If there were no emissions, Promise\n * resolves with undefined.\n * @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise\n */\n toPromise(promiseCtor?: PromiseConstructorLike): Promise {\n promiseCtor = getPromiseCtor(promiseCtor);\n\n return new promiseCtor((resolve, reject) => {\n let value: T | undefined;\n this.subscribe(\n (x: T) => (value = x),\n (err: any) => reject(err),\n () => resolve(value)\n );\n }) as Promise;\n }\n}\n\n/**\n * Decides between a passed promise constructor from consuming code,\n * A default configured promise constructor, and the native promise\n * constructor and returns it. If nothing can be found, it will throw\n * an error.\n * @param promiseCtor The optional promise constructor to passed by consuming code\n */\nfunction getPromiseCtor(promiseCtor: PromiseConstructorLike | undefined) {\n return promiseCtor ?? config.Promise ?? Promise;\n}\n\nfunction isObserver(value: any): value is Observer {\n return value && isFunction(value.next) && isFunction(value.error) && isFunction(value.complete);\n}\n\nfunction isSubscriber(value: any): value is Subscriber {\n return (value && value instanceof Subscriber) || (isObserver(value) && isSubscription(value));\n}\n", "import { Observable } from '../Observable';\nimport { Subscriber } from '../Subscriber';\nimport { OperatorFunction } from '../types';\nimport { isFunction } from './isFunction';\n\n/**\n * Used to determine if an object is an Observable with a lift function.\n */\nexport function hasLift(source: any): source is { lift: InstanceType['lift'] } {\n return isFunction(source?.lift);\n}\n\n/**\n * Creates an `OperatorFunction`. Used to define operators throughout the library in a concise way.\n * @param init The logic to connect the liftedSource to the subscriber at the moment of subscription.\n */\nexport function operate(\n init: (liftedSource: Observable, subscriber: Subscriber) => (() => void) | void\n): OperatorFunction {\n return (source: Observable) => {\n if (hasLift(source)) {\n return source.lift(function (this: Subscriber, liftedSource: Observable) {\n try {\n return init(liftedSource, this);\n } catch (err) {\n this.error(err);\n }\n });\n }\n throw new TypeError('Unable to lift unknown Observable type');\n };\n}\n", "import { Subscriber } from '../Subscriber';\n\n/**\n * Creates an instance of an `OperatorSubscriber`.\n * @param destination The downstream subscriber.\n * @param onNext Handles next values, only called if this subscriber is not stopped or closed. Any\n * error that occurs in this function is caught and sent to the `error` method of this subscriber.\n * @param onError Handles errors from the subscription, any errors that occur in this handler are caught\n * and send to the `destination` error handler.\n * @param onComplete Handles completion notification from the subscription. Any errors that occur in\n * this handler are sent to the `destination` error handler.\n * @param onFinalize Additional teardown logic here. This will only be called on teardown if the\n * subscriber itself is not already closed. This is called after all other teardown logic is executed.\n */\nexport function createOperatorSubscriber(\n destination: Subscriber,\n onNext?: (value: T) => void,\n onComplete?: () => void,\n onError?: (err: any) => void,\n onFinalize?: () => void\n): Subscriber {\n return new OperatorSubscriber(destination, onNext, onComplete, onError, onFinalize);\n}\n\n/**\n * A generic helper for allowing operators to be created with a Subscriber and\n * use closures to capture necessary state from the operator function itself.\n */\nexport class OperatorSubscriber extends Subscriber {\n /**\n * Creates an instance of an `OperatorSubscriber`.\n * @param destination The downstream subscriber.\n * @param onNext Handles next values, only called if this subscriber is not stopped or closed. Any\n * error that occurs in this function is caught and sent to the `error` method of this subscriber.\n * @param onError Handles errors from the subscription, any errors that occur in this handler are caught\n * and send to the `destination` error handler.\n * @param onComplete Handles completion notification from the subscription. Any errors that occur in\n * this handler are sent to the `destination` error handler.\n * @param onFinalize Additional finalization logic here. This will only be called on finalization if the\n * subscriber itself is not already closed. This is called after all other finalization logic is executed.\n * @param shouldUnsubscribe An optional check to see if an unsubscribe call should truly unsubscribe.\n * NOTE: This currently **ONLY** exists to support the strange behavior of {@link groupBy}, where unsubscription\n * to the resulting observable does not actually disconnect from the source if there are active subscriptions\n * to any grouped observable. (DO NOT EXPOSE OR USE EXTERNALLY!!!)\n */\n constructor(\n destination: Subscriber,\n onNext?: (value: T) => void,\n onComplete?: () => void,\n onError?: (err: any) => void,\n private onFinalize?: () => void,\n private shouldUnsubscribe?: () => boolean\n ) {\n // It's important - for performance reasons - that all of this class's\n // members are initialized and that they are always initialized in the same\n // order. This will ensure that all OperatorSubscriber instances have the\n // same hidden class in V8. This, in turn, will help keep the number of\n // hidden classes involved in property accesses within the base class as\n // low as possible. If the number of hidden classes involved exceeds four,\n // the property accesses will become megamorphic and performance penalties\n // will be incurred - i.e. inline caches won't be used.\n //\n // The reasons for ensuring all instances have the same hidden class are\n // further discussed in this blog post from Benedikt Meurer:\n // https://benediktmeurer.de/2018/03/23/impact-of-polymorphism-on-component-based-frameworks-like-react/\n super(destination);\n this._next = onNext\n ? function (this: OperatorSubscriber, value: T) {\n try {\n onNext(value);\n } catch (err) {\n destination.error(err);\n }\n }\n : super._next;\n this._error = onError\n ? function (this: OperatorSubscriber, err: any) {\n try {\n onError(err);\n } catch (err) {\n // Send any errors that occur down stream.\n destination.error(err);\n } finally {\n // Ensure finalization.\n this.unsubscribe();\n }\n }\n : super._error;\n this._complete = onComplete\n ? function (this: OperatorSubscriber) {\n try {\n onComplete();\n } catch (err) {\n // Send any errors that occur down stream.\n destination.error(err);\n } finally {\n // Ensure finalization.\n this.unsubscribe();\n }\n }\n : super._complete;\n }\n\n unsubscribe() {\n if (!this.shouldUnsubscribe || this.shouldUnsubscribe()) {\n const { closed } = this;\n super.unsubscribe();\n // Execute additional teardown if we have any and we didn't already do so.\n !closed && this.onFinalize?.();\n }\n }\n}\n", "import { Subscription } from '../Subscription';\n\ninterface AnimationFrameProvider {\n schedule(callback: FrameRequestCallback): Subscription;\n requestAnimationFrame: typeof requestAnimationFrame;\n cancelAnimationFrame: typeof cancelAnimationFrame;\n delegate:\n | {\n requestAnimationFrame: typeof requestAnimationFrame;\n cancelAnimationFrame: typeof cancelAnimationFrame;\n }\n | undefined;\n}\n\nexport const animationFrameProvider: AnimationFrameProvider = {\n // When accessing the delegate, use the variable rather than `this` so that\n // the functions can be called without being bound to the provider.\n schedule(callback) {\n let request = requestAnimationFrame;\n let cancel: typeof cancelAnimationFrame | undefined = cancelAnimationFrame;\n const { delegate } = animationFrameProvider;\n if (delegate) {\n request = delegate.requestAnimationFrame;\n cancel = delegate.cancelAnimationFrame;\n }\n const handle = request((timestamp) => {\n // Clear the cancel function. The request has been fulfilled, so\n // attempting to cancel the request upon unsubscription would be\n // pointless.\n cancel = undefined;\n callback(timestamp);\n });\n return new Subscription(() => cancel?.(handle));\n },\n requestAnimationFrame(...args) {\n const { delegate } = animationFrameProvider;\n return (delegate?.requestAnimationFrame || requestAnimationFrame)(...args);\n },\n cancelAnimationFrame(...args) {\n const { delegate } = animationFrameProvider;\n return (delegate?.cancelAnimationFrame || cancelAnimationFrame)(...args);\n },\n delegate: undefined,\n};\n", "import { createErrorClass } from './createErrorClass';\n\nexport interface ObjectUnsubscribedError extends Error {}\n\nexport interface ObjectUnsubscribedErrorCtor {\n /**\n * @deprecated Internal implementation detail. Do not construct error instances.\n * Cannot be tagged as internal: https://github.com/ReactiveX/rxjs/issues/6269\n */\n new (): ObjectUnsubscribedError;\n}\n\n/**\n * An error thrown when an action is invalid because the object has been\n * unsubscribed.\n *\n * @see {@link Subject}\n * @see {@link BehaviorSubject}\n *\n * @class ObjectUnsubscribedError\n */\nexport const ObjectUnsubscribedError: ObjectUnsubscribedErrorCtor = createErrorClass(\n (_super) =>\n function ObjectUnsubscribedErrorImpl(this: any) {\n _super(this);\n this.name = 'ObjectUnsubscribedError';\n this.message = 'object unsubscribed';\n }\n);\n", "import { Operator } from './Operator';\nimport { Observable } from './Observable';\nimport { Subscriber } from './Subscriber';\nimport { Subscription, EMPTY_SUBSCRIPTION } from './Subscription';\nimport { Observer, SubscriptionLike, TeardownLogic } from './types';\nimport { ObjectUnsubscribedError } from './util/ObjectUnsubscribedError';\nimport { arrRemove } from './util/arrRemove';\nimport { errorContext } from './util/errorContext';\n\n/**\n * A Subject is a special type of Observable that allows values to be\n * multicasted to many Observers. Subjects are like EventEmitters.\n *\n * Every Subject is an Observable and an Observer. You can subscribe to a\n * Subject, and you can call next to feed values as well as error and complete.\n */\nexport class Subject extends Observable implements SubscriptionLike {\n closed = false;\n\n private currentObservers: Observer[] | null = null;\n\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n observers: Observer[] = [];\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n isStopped = false;\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n hasError = false;\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n thrownError: any = null;\n\n /**\n * Creates a \"subject\" by basically gluing an observer to an observable.\n *\n * @deprecated Recommended you do not use. Will be removed at some point in the future. Plans for replacement still under discussion.\n */\n static create: (...args: any[]) => any = (destination: Observer, source: Observable): AnonymousSubject => {\n return new AnonymousSubject(destination, source);\n };\n\n constructor() {\n // NOTE: This must be here to obscure Observable's constructor.\n super();\n }\n\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n lift(operator: Operator): Observable {\n const subject = new AnonymousSubject(this, this);\n subject.operator = operator as any;\n return subject as any;\n }\n\n /** @internal */\n protected _throwIfClosed() {\n if (this.closed) {\n throw new ObjectUnsubscribedError();\n }\n }\n\n next(value: T) {\n errorContext(() => {\n this._throwIfClosed();\n if (!this.isStopped) {\n if (!this.currentObservers) {\n this.currentObservers = Array.from(this.observers);\n }\n for (const observer of this.currentObservers) {\n observer.next(value);\n }\n }\n });\n }\n\n error(err: any) {\n errorContext(() => {\n this._throwIfClosed();\n if (!this.isStopped) {\n this.hasError = this.isStopped = true;\n this.thrownError = err;\n const { observers } = this;\n while (observers.length) {\n observers.shift()!.error(err);\n }\n }\n });\n }\n\n complete() {\n errorContext(() => {\n this._throwIfClosed();\n if (!this.isStopped) {\n this.isStopped = true;\n const { observers } = this;\n while (observers.length) {\n observers.shift()!.complete();\n }\n }\n });\n }\n\n unsubscribe() {\n this.isStopped = this.closed = true;\n this.observers = this.currentObservers = null!;\n }\n\n get observed() {\n return this.observers?.length > 0;\n }\n\n /** @internal */\n protected _trySubscribe(subscriber: Subscriber): TeardownLogic {\n this._throwIfClosed();\n return super._trySubscribe(subscriber);\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n this._throwIfClosed();\n this._checkFinalizedStatuses(subscriber);\n return this._innerSubscribe(subscriber);\n }\n\n /** @internal */\n protected _innerSubscribe(subscriber: Subscriber) {\n const { hasError, isStopped, observers } = this;\n if (hasError || isStopped) {\n return EMPTY_SUBSCRIPTION;\n }\n this.currentObservers = null;\n observers.push(subscriber);\n return new Subscription(() => {\n this.currentObservers = null;\n arrRemove(observers, subscriber);\n });\n }\n\n /** @internal */\n protected _checkFinalizedStatuses(subscriber: Subscriber) {\n const { hasError, thrownError, isStopped } = this;\n if (hasError) {\n subscriber.error(thrownError);\n } else if (isStopped) {\n subscriber.complete();\n }\n }\n\n /**\n * Creates a new Observable with this Subject as the source. You can do this\n * to create custom Observer-side logic of the Subject and conceal it from\n * code that uses the Observable.\n * @return Observable that this Subject casts to.\n */\n asObservable(): Observable {\n const observable: any = new Observable();\n observable.source = this;\n return observable;\n }\n}\n\nexport class AnonymousSubject extends Subject {\n constructor(\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n public destination?: Observer,\n source?: Observable\n ) {\n super();\n this.source = source;\n }\n\n next(value: T) {\n this.destination?.next?.(value);\n }\n\n error(err: any) {\n this.destination?.error?.(err);\n }\n\n complete() {\n this.destination?.complete?.();\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n return this.source?.subscribe(subscriber) ?? EMPTY_SUBSCRIPTION;\n }\n}\n", "import { Subject } from './Subject';\nimport { Subscriber } from './Subscriber';\nimport { Subscription } from './Subscription';\n\n/**\n * A variant of Subject that requires an initial value and emits its current\n * value whenever it is subscribed to.\n */\nexport class BehaviorSubject extends Subject {\n constructor(private _value: T) {\n super();\n }\n\n get value(): T {\n return this.getValue();\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n const subscription = super._subscribe(subscriber);\n !subscription.closed && subscriber.next(this._value);\n return subscription;\n }\n\n getValue(): T {\n const { hasError, thrownError, _value } = this;\n if (hasError) {\n throw thrownError;\n }\n this._throwIfClosed();\n return _value;\n }\n\n next(value: T): void {\n super.next((this._value = value));\n }\n}\n", "import { TimestampProvider } from '../types';\n\ninterface DateTimestampProvider extends TimestampProvider {\n delegate: TimestampProvider | undefined;\n}\n\nexport const dateTimestampProvider: DateTimestampProvider = {\n now() {\n // Use the variable rather than `this` so that the function can be called\n // without being bound to the provider.\n return (dateTimestampProvider.delegate || Date).now();\n },\n delegate: undefined,\n};\n", "import { Subject } from './Subject';\nimport { TimestampProvider } from './types';\nimport { Subscriber } from './Subscriber';\nimport { Subscription } from './Subscription';\nimport { dateTimestampProvider } from './scheduler/dateTimestampProvider';\n\n/**\n * A variant of {@link Subject} that \"replays\" old values to new subscribers by emitting them when they first subscribe.\n *\n * `ReplaySubject` has an internal buffer that will store a specified number of values that it has observed. Like `Subject`,\n * `ReplaySubject` \"observes\" values by having them passed to its `next` method. When it observes a value, it will store that\n * value for a time determined by the configuration of the `ReplaySubject`, as passed to its constructor.\n *\n * When a new subscriber subscribes to the `ReplaySubject` instance, it will synchronously emit all values in its buffer in\n * a First-In-First-Out (FIFO) manner. The `ReplaySubject` will also complete, if it has observed completion; and it will\n * error if it has observed an error.\n *\n * There are two main configuration items to be concerned with:\n *\n * 1. `bufferSize` - This will determine how many items are stored in the buffer, defaults to infinite.\n * 2. `windowTime` - The amount of time to hold a value in the buffer before removing it from the buffer.\n *\n * Both configurations may exist simultaneously. So if you would like to buffer a maximum of 3 values, as long as the values\n * are less than 2 seconds old, you could do so with a `new ReplaySubject(3, 2000)`.\n *\n * ### Differences with BehaviorSubject\n *\n * `BehaviorSubject` is similar to `new ReplaySubject(1)`, with a couple of exceptions:\n *\n * 1. `BehaviorSubject` comes \"primed\" with a single value upon construction.\n * 2. `ReplaySubject` will replay values, even after observing an error, where `BehaviorSubject` will not.\n *\n * @see {@link Subject}\n * @see {@link BehaviorSubject}\n * @see {@link shareReplay}\n */\nexport class ReplaySubject extends Subject {\n private _buffer: (T | number)[] = [];\n private _infiniteTimeWindow = true;\n\n /**\n * @param _bufferSize The size of the buffer to replay on subscription\n * @param _windowTime The amount of time the buffered items will stay buffered\n * @param _timestampProvider An object with a `now()` method that provides the current timestamp. This is used to\n * calculate the amount of time something has been buffered.\n */\n constructor(\n private _bufferSize = Infinity,\n private _windowTime = Infinity,\n private _timestampProvider: TimestampProvider = dateTimestampProvider\n ) {\n super();\n this._infiniteTimeWindow = _windowTime === Infinity;\n this._bufferSize = Math.max(1, _bufferSize);\n this._windowTime = Math.max(1, _windowTime);\n }\n\n next(value: T): void {\n const { isStopped, _buffer, _infiniteTimeWindow, _timestampProvider, _windowTime } = this;\n if (!isStopped) {\n _buffer.push(value);\n !_infiniteTimeWindow && _buffer.push(_timestampProvider.now() + _windowTime);\n }\n this._trimBuffer();\n super.next(value);\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n this._throwIfClosed();\n this._trimBuffer();\n\n const subscription = this._innerSubscribe(subscriber);\n\n const { _infiniteTimeWindow, _buffer } = this;\n // We use a copy here, so reentrant code does not mutate our array while we're\n // emitting it to a new subscriber.\n const copy = _buffer.slice();\n for (let i = 0; i < copy.length && !subscriber.closed; i += _infiniteTimeWindow ? 1 : 2) {\n subscriber.next(copy[i] as T);\n }\n\n this._checkFinalizedStatuses(subscriber);\n\n return subscription;\n }\n\n private _trimBuffer() {\n const { _bufferSize, _timestampProvider, _buffer, _infiniteTimeWindow } = this;\n // If we don't have an infinite buffer size, and we're over the length,\n // use splice to truncate the old buffer values off. Note that we have to\n // double the size for instances where we're not using an infinite time window\n // because we're storing the values and the timestamps in the same array.\n const adjustedBufferSize = (_infiniteTimeWindow ? 1 : 2) * _bufferSize;\n _bufferSize < Infinity && adjustedBufferSize < _buffer.length && _buffer.splice(0, _buffer.length - adjustedBufferSize);\n\n // Now, if we're not in an infinite time window, remove all values where the time is\n // older than what is allowed.\n if (!_infiniteTimeWindow) {\n const now = _timestampProvider.now();\n let last = 0;\n // Search the array for the first timestamp that isn't expired and\n // truncate the buffer up to that point.\n for (let i = 1; i < _buffer.length && (_buffer[i] as number) <= now; i += 2) {\n last = i;\n }\n last && _buffer.splice(0, last + 1);\n }\n }\n}\n", "import { Scheduler } from '../Scheduler';\nimport { Subscription } from '../Subscription';\nimport { SchedulerAction } from '../types';\n\n/**\n * A unit of work to be executed in a `scheduler`. An action is typically\n * created from within a {@link SchedulerLike} and an RxJS user does not need to concern\n * themselves about creating and manipulating an Action.\n *\n * ```ts\n * class Action extends Subscription {\n * new (scheduler: Scheduler, work: (state?: T) => void);\n * schedule(state?: T, delay: number = 0): Subscription;\n * }\n * ```\n */\nexport class Action extends Subscription {\n constructor(scheduler: Scheduler, work: (this: SchedulerAction, state?: T) => void) {\n super();\n }\n /**\n * Schedules this action on its parent {@link SchedulerLike} for execution. May be passed\n * some context object, `state`. May happen at some point in the future,\n * according to the `delay` parameter, if specified.\n * @param state Some contextual data that the `work` function uses when called by the\n * Scheduler.\n * @param delay Time to wait before executing the work, where the time unit is implicit\n * and defined by the Scheduler.\n * @return A subscription in order to be able to unsubscribe the scheduled work.\n */\n public schedule(state?: T, delay: number = 0): Subscription {\n return this;\n }\n}\n", "import type { TimerHandle } from './timerHandle';\ntype SetIntervalFunction = (handler: () => void, timeout?: number, ...args: any[]) => TimerHandle;\ntype ClearIntervalFunction = (handle: TimerHandle) => void;\n\ninterface IntervalProvider {\n setInterval: SetIntervalFunction;\n clearInterval: ClearIntervalFunction;\n delegate:\n | {\n setInterval: SetIntervalFunction;\n clearInterval: ClearIntervalFunction;\n }\n | undefined;\n}\n\nexport const intervalProvider: IntervalProvider = {\n // When accessing the delegate, use the variable rather than `this` so that\n // the functions can be called without being bound to the provider.\n setInterval(handler: () => void, timeout?: number, ...args) {\n const { delegate } = intervalProvider;\n if (delegate?.setInterval) {\n return delegate.setInterval(handler, timeout, ...args);\n }\n return setInterval(handler, timeout, ...args);\n },\n clearInterval(handle) {\n const { delegate } = intervalProvider;\n return (delegate?.clearInterval || clearInterval)(handle as any);\n },\n delegate: undefined,\n};\n", "import { Action } from './Action';\nimport { SchedulerAction } from '../types';\nimport { Subscription } from '../Subscription';\nimport { AsyncScheduler } from './AsyncScheduler';\nimport { intervalProvider } from './intervalProvider';\nimport { arrRemove } from '../util/arrRemove';\nimport { TimerHandle } from './timerHandle';\n\nexport class AsyncAction extends Action {\n public id: TimerHandle | undefined;\n public state?: T;\n // @ts-ignore: Property has no initializer and is not definitely assigned\n public delay: number;\n protected pending: boolean = false;\n\n constructor(protected scheduler: AsyncScheduler, protected work: (this: SchedulerAction, state?: T) => void) {\n super(scheduler, work);\n }\n\n public schedule(state?: T, delay: number = 0): Subscription {\n if (this.closed) {\n return this;\n }\n\n // Always replace the current state with the new state.\n this.state = state;\n\n const id = this.id;\n const scheduler = this.scheduler;\n\n //\n // Important implementation note:\n //\n // Actions only execute once by default, unless rescheduled from within the\n // scheduled callback. This allows us to implement single and repeat\n // actions via the same code path, without adding API surface area, as well\n // as mimic traditional recursion but across asynchronous boundaries.\n //\n // However, JS runtimes and timers distinguish between intervals achieved by\n // serial `setTimeout` calls vs. a single `setInterval` call. An interval of\n // serial `setTimeout` calls can be individually delayed, which delays\n // scheduling the next `setTimeout`, and so on. `setInterval` attempts to\n // guarantee the interval callback will be invoked more precisely to the\n // interval period, regardless of load.\n //\n // Therefore, we use `setInterval` to schedule single and repeat actions.\n // If the action reschedules itself with the same delay, the interval is not\n // canceled. If the action doesn't reschedule, or reschedules with a\n // different delay, the interval will be canceled after scheduled callback\n // execution.\n //\n if (id != null) {\n this.id = this.recycleAsyncId(scheduler, id, delay);\n }\n\n // Set the pending flag indicating that this action has been scheduled, or\n // has recursively rescheduled itself.\n this.pending = true;\n\n this.delay = delay;\n // If this action has already an async Id, don't request a new one.\n this.id = this.id ?? this.requestAsyncId(scheduler, this.id, delay);\n\n return this;\n }\n\n protected requestAsyncId(scheduler: AsyncScheduler, _id?: TimerHandle, delay: number = 0): TimerHandle {\n return intervalProvider.setInterval(scheduler.flush.bind(scheduler, this), delay);\n }\n\n protected recycleAsyncId(_scheduler: AsyncScheduler, id?: TimerHandle, delay: number | null = 0): TimerHandle | undefined {\n // If this action is rescheduled with the same delay time, don't clear the interval id.\n if (delay != null && this.delay === delay && this.pending === false) {\n return id;\n }\n // Otherwise, if the action's delay time is different from the current delay,\n // or the action has been rescheduled before it's executed, clear the interval id\n if (id != null) {\n intervalProvider.clearInterval(id);\n }\n\n return undefined;\n }\n\n /**\n * Immediately executes this action and the `work` it contains.\n */\n public execute(state: T, delay: number): any {\n if (this.closed) {\n return new Error('executing a cancelled action');\n }\n\n this.pending = false;\n const error = this._execute(state, delay);\n if (error) {\n return error;\n } else if (this.pending === false && this.id != null) {\n // Dequeue if the action didn't reschedule itself. Don't call\n // unsubscribe(), because the action could reschedule later.\n // For example:\n // ```\n // scheduler.schedule(function doWork(counter) {\n // /* ... I'm a busy worker bee ... */\n // var originalAction = this;\n // /* wait 100ms before rescheduling the action */\n // setTimeout(function () {\n // originalAction.schedule(counter + 1);\n // }, 100);\n // }, 1000);\n // ```\n this.id = this.recycleAsyncId(this.scheduler, this.id, null);\n }\n }\n\n protected _execute(state: T, _delay: number): any {\n let errored: boolean = false;\n let errorValue: any;\n try {\n this.work(state);\n } catch (e) {\n errored = true;\n // HACK: Since code elsewhere is relying on the \"truthiness\" of the\n // return here, we can't have it return \"\" or 0 or false.\n // TODO: Clean this up when we refactor schedulers mid-version-8 or so.\n errorValue = e ? e : new Error('Scheduled action threw falsy error');\n }\n if (errored) {\n this.unsubscribe();\n return errorValue;\n }\n }\n\n unsubscribe() {\n if (!this.closed) {\n const { id, scheduler } = this;\n const { actions } = scheduler;\n\n this.work = this.state = this.scheduler = null!;\n this.pending = false;\n\n arrRemove(actions, this);\n if (id != null) {\n this.id = this.recycleAsyncId(scheduler, id, null);\n }\n\n this.delay = null!;\n super.unsubscribe();\n }\n }\n}\n", "import { Action } from './scheduler/Action';\nimport { Subscription } from './Subscription';\nimport { SchedulerLike, SchedulerAction } from './types';\nimport { dateTimestampProvider } from './scheduler/dateTimestampProvider';\n\n/**\n * An execution context and a data structure to order tasks and schedule their\n * execution. Provides a notion of (potentially virtual) time, through the\n * `now()` getter method.\n *\n * Each unit of work in a Scheduler is called an `Action`.\n *\n * ```ts\n * class Scheduler {\n * now(): number;\n * schedule(work, delay?, state?): Subscription;\n * }\n * ```\n *\n * @deprecated Scheduler is an internal implementation detail of RxJS, and\n * should not be used directly. Rather, create your own class and implement\n * {@link SchedulerLike}. Will be made internal in v8.\n */\nexport class Scheduler implements SchedulerLike {\n public static now: () => number = dateTimestampProvider.now;\n\n constructor(private schedulerActionCtor: typeof Action, now: () => number = Scheduler.now) {\n this.now = now;\n }\n\n /**\n * A getter method that returns a number representing the current time\n * (at the time this function was called) according to the scheduler's own\n * internal clock.\n * @return A number that represents the current time. May or may not\n * have a relation to wall-clock time. May or may not refer to a time unit\n * (e.g. milliseconds).\n */\n public now: () => number;\n\n /**\n * Schedules a function, `work`, for execution. May happen at some point in\n * the future, according to the `delay` parameter, if specified. May be passed\n * some context object, `state`, which will be passed to the `work` function.\n *\n * The given arguments will be processed an stored as an Action object in a\n * queue of actions.\n *\n * @param work A function representing a task, or some unit of work to be\n * executed by the Scheduler.\n * @param delay Time to wait before executing the work, where the time unit is\n * implicit and defined by the Scheduler itself.\n * @param state Some contextual data that the `work` function uses when called\n * by the Scheduler.\n * @return A subscription in order to be able to unsubscribe the scheduled work.\n */\n public schedule(work: (this: SchedulerAction, state?: T) => void, delay: number = 0, state?: T): Subscription {\n return new this.schedulerActionCtor(this, work).schedule(state, delay);\n }\n}\n", "import { Scheduler } from '../Scheduler';\nimport { Action } from './Action';\nimport { AsyncAction } from './AsyncAction';\nimport { TimerHandle } from './timerHandle';\n\nexport class AsyncScheduler extends Scheduler {\n public actions: Array> = [];\n /**\n * A flag to indicate whether the Scheduler is currently executing a batch of\n * queued actions.\n * @internal\n */\n public _active: boolean = false;\n /**\n * An internal ID used to track the latest asynchronous task such as those\n * coming from `setTimeout`, `setInterval`, `requestAnimationFrame`, and\n * others.\n * @internal\n */\n public _scheduled: TimerHandle | undefined;\n\n constructor(SchedulerAction: typeof Action, now: () => number = Scheduler.now) {\n super(SchedulerAction, now);\n }\n\n public flush(action: AsyncAction): void {\n const { actions } = this;\n\n if (this._active) {\n actions.push(action);\n return;\n }\n\n let error: any;\n this._active = true;\n\n do {\n if ((error = action.execute(action.state, action.delay))) {\n break;\n }\n } while ((action = actions.shift()!)); // exhaust the scheduler queue\n\n this._active = false;\n\n if (error) {\n while ((action = actions.shift()!)) {\n action.unsubscribe();\n }\n throw error;\n }\n }\n}\n", "import { AsyncAction } from './AsyncAction';\nimport { AsyncScheduler } from './AsyncScheduler';\n\n/**\n *\n * Async Scheduler\n *\n * Schedule task as if you used setTimeout(task, duration)\n *\n * `async` scheduler schedules tasks asynchronously, by putting them on the JavaScript\n * event loop queue. It is best used to delay tasks in time or to schedule tasks repeating\n * in intervals.\n *\n * If you just want to \"defer\" task, that is to perform it right after currently\n * executing synchronous code ends (commonly achieved by `setTimeout(deferredTask, 0)`),\n * better choice will be the {@link asapScheduler} scheduler.\n *\n * ## Examples\n * Use async scheduler to delay task\n * ```ts\n * import { asyncScheduler } from 'rxjs';\n *\n * const task = () => console.log('it works!');\n *\n * asyncScheduler.schedule(task, 2000);\n *\n * // After 2 seconds logs:\n * // \"it works!\"\n * ```\n *\n * Use async scheduler to repeat task in intervals\n * ```ts\n * import { asyncScheduler } from 'rxjs';\n *\n * function task(state) {\n * console.log(state);\n * this.schedule(state + 1, 1000); // `this` references currently executing Action,\n * // which we reschedule with new state and delay\n * }\n *\n * asyncScheduler.schedule(task, 3000, 0);\n *\n * // Logs:\n * // 0 after 3s\n * // 1 after 4s\n * // 2 after 5s\n * // 3 after 6s\n * ```\n */\n\nexport const asyncScheduler = new AsyncScheduler(AsyncAction);\n\n/**\n * @deprecated Renamed to {@link asyncScheduler}. Will be removed in v8.\n */\nexport const async = asyncScheduler;\n", "import { AsyncAction } from './AsyncAction';\nimport { Subscription } from '../Subscription';\nimport { QueueScheduler } from './QueueScheduler';\nimport { SchedulerAction } from '../types';\nimport { TimerHandle } from './timerHandle';\n\nexport class QueueAction extends AsyncAction {\n constructor(protected scheduler: QueueScheduler, protected work: (this: SchedulerAction, state?: T) => void) {\n super(scheduler, work);\n }\n\n public schedule(state?: T, delay: number = 0): Subscription {\n if (delay > 0) {\n return super.schedule(state, delay);\n }\n this.delay = delay;\n this.state = state;\n this.scheduler.flush(this);\n return this;\n }\n\n public execute(state: T, delay: number): any {\n return delay > 0 || this.closed ? super.execute(state, delay) : this._execute(state, delay);\n }\n\n protected requestAsyncId(scheduler: QueueScheduler, id?: TimerHandle, delay: number = 0): TimerHandle {\n // If delay exists and is greater than 0, or if the delay is null (the\n // action wasn't rescheduled) but was originally scheduled as an async\n // action, then recycle as an async action.\n\n if ((delay != null && delay > 0) || (delay == null && this.delay > 0)) {\n return super.requestAsyncId(scheduler, id, delay);\n }\n\n // Otherwise flush the scheduler starting with this action.\n scheduler.flush(this);\n\n // HACK: In the past, this was returning `void`. However, `void` isn't a valid\n // `TimerHandle`, and generally the return value here isn't really used. So the\n // compromise is to return `0` which is both \"falsy\" and a valid `TimerHandle`,\n // as opposed to refactoring every other instanceo of `requestAsyncId`.\n return 0;\n }\n}\n", "import { AsyncScheduler } from './AsyncScheduler';\n\nexport class QueueScheduler extends AsyncScheduler {\n}\n", "import { QueueAction } from './QueueAction';\nimport { QueueScheduler } from './QueueScheduler';\n\n/**\n *\n * Queue Scheduler\n *\n * Put every next task on a queue, instead of executing it immediately\n *\n * `queue` scheduler, when used with delay, behaves the same as {@link asyncScheduler} scheduler.\n *\n * When used without delay, it schedules given task synchronously - executes it right when\n * it is scheduled. However when called recursively, that is when inside the scheduled task,\n * another task is scheduled with queue scheduler, instead of executing immediately as well,\n * that task will be put on a queue and wait for current one to finish.\n *\n * This means that when you execute task with `queue` scheduler, you are sure it will end\n * before any other task scheduled with that scheduler will start.\n *\n * ## Examples\n * Schedule recursively first, then do something\n * ```ts\n * import { queueScheduler } from 'rxjs';\n *\n * queueScheduler.schedule(() => {\n * queueScheduler.schedule(() => console.log('second')); // will not happen now, but will be put on a queue\n *\n * console.log('first');\n * });\n *\n * // Logs:\n * // \"first\"\n * // \"second\"\n * ```\n *\n * Reschedule itself recursively\n * ```ts\n * import { queueScheduler } from 'rxjs';\n *\n * queueScheduler.schedule(function(state) {\n * if (state !== 0) {\n * console.log('before', state);\n * this.schedule(state - 1); // `this` references currently executing Action,\n * // which we reschedule with new state\n * console.log('after', state);\n * }\n * }, 0, 3);\n *\n * // In scheduler that runs recursively, you would expect:\n * // \"before\", 3\n * // \"before\", 2\n * // \"before\", 1\n * // \"after\", 1\n * // \"after\", 2\n * // \"after\", 3\n *\n * // But with queue it logs:\n * // \"before\", 3\n * // \"after\", 3\n * // \"before\", 2\n * // \"after\", 2\n * // \"before\", 1\n * // \"after\", 1\n * ```\n */\n\nexport const queueScheduler = new QueueScheduler(QueueAction);\n\n/**\n * @deprecated Renamed to {@link queueScheduler}. Will be removed in v8.\n */\nexport const queue = queueScheduler;\n", "import { AsyncAction } from './AsyncAction';\nimport { AnimationFrameScheduler } from './AnimationFrameScheduler';\nimport { SchedulerAction } from '../types';\nimport { animationFrameProvider } from './animationFrameProvider';\nimport { TimerHandle } from './timerHandle';\n\nexport class AnimationFrameAction extends AsyncAction {\n constructor(protected scheduler: AnimationFrameScheduler, protected work: (this: SchedulerAction, state?: T) => void) {\n super(scheduler, work);\n }\n\n protected requestAsyncId(scheduler: AnimationFrameScheduler, id?: TimerHandle, delay: number = 0): TimerHandle {\n // If delay is greater than 0, request as an async action.\n if (delay !== null && delay > 0) {\n return super.requestAsyncId(scheduler, id, delay);\n }\n // Push the action to the end of the scheduler queue.\n scheduler.actions.push(this);\n // If an animation frame has already been requested, don't request another\n // one. If an animation frame hasn't been requested yet, request one. Return\n // the current animation frame request id.\n return scheduler._scheduled || (scheduler._scheduled = animationFrameProvider.requestAnimationFrame(() => scheduler.flush(undefined)));\n }\n\n protected recycleAsyncId(scheduler: AnimationFrameScheduler, id?: TimerHandle, delay: number = 0): TimerHandle | undefined {\n // If delay exists and is greater than 0, or if the delay is null (the\n // action wasn't rescheduled) but was originally scheduled as an async\n // action, then recycle as an async action.\n if (delay != null ? delay > 0 : this.delay > 0) {\n return super.recycleAsyncId(scheduler, id, delay);\n }\n // If the scheduler queue has no remaining actions with the same async id,\n // cancel the requested animation frame and set the scheduled flag to\n // undefined so the next AnimationFrameAction will request its own.\n const { actions } = scheduler;\n if (id != null && id === scheduler._scheduled && actions[actions.length - 1]?.id !== id) {\n animationFrameProvider.cancelAnimationFrame(id as number);\n scheduler._scheduled = undefined;\n }\n // Return undefined so the action knows to request a new async id if it's rescheduled.\n return undefined;\n }\n}\n", "import { AsyncAction } from './AsyncAction';\nimport { AsyncScheduler } from './AsyncScheduler';\n\nexport class AnimationFrameScheduler extends AsyncScheduler {\n public flush(action?: AsyncAction): void {\n this._active = true;\n // The async id that effects a call to flush is stored in _scheduled.\n // Before executing an action, it's necessary to check the action's async\n // id to determine whether it's supposed to be executed in the current\n // flush.\n // Previous implementations of this method used a count to determine this,\n // but that was unsound, as actions that are unsubscribed - i.e. cancelled -\n // are removed from the actions array and that can shift actions that are\n // scheduled to be executed in a subsequent flush into positions at which\n // they are executed within the current flush.\n let flushId;\n if (action) {\n flushId = action.id;\n } else {\n flushId = this._scheduled;\n this._scheduled = undefined;\n }\n\n const { actions } = this;\n let error: any;\n action = action || actions.shift()!;\n\n do {\n if ((error = action.execute(action.state, action.delay))) {\n break;\n }\n } while ((action = actions[0]) && action.id === flushId && actions.shift());\n\n this._active = false;\n\n if (error) {\n while ((action = actions[0]) && action.id === flushId && actions.shift()) {\n action.unsubscribe();\n }\n throw error;\n }\n }\n}\n", "import { AnimationFrameAction } from './AnimationFrameAction';\nimport { AnimationFrameScheduler } from './AnimationFrameScheduler';\n\n/**\n *\n * Animation Frame Scheduler\n *\n * Perform task when `window.requestAnimationFrame` would fire\n *\n * When `animationFrame` scheduler is used with delay, it will fall back to {@link asyncScheduler} scheduler\n * behaviour.\n *\n * Without delay, `animationFrame` scheduler can be used to create smooth browser animations.\n * It makes sure scheduled task will happen just before next browser content repaint,\n * thus performing animations as efficiently as possible.\n *\n * ## Example\n * Schedule div height animation\n * ```ts\n * // html:
    \n * import { animationFrameScheduler } from 'rxjs';\n *\n * const div = document.querySelector('div');\n *\n * animationFrameScheduler.schedule(function(height) {\n * div.style.height = height + \"px\";\n *\n * this.schedule(height + 1); // `this` references currently executing Action,\n * // which we reschedule with new state\n * }, 0, 0);\n *\n * // You will see a div element growing in height\n * ```\n */\n\nexport const animationFrameScheduler = new AnimationFrameScheduler(AnimationFrameAction);\n\n/**\n * @deprecated Renamed to {@link animationFrameScheduler}. Will be removed in v8.\n */\nexport const animationFrame = animationFrameScheduler;\n", "import { Observable } from '../Observable';\nimport { SchedulerLike } from '../types';\n\n/**\n * A simple Observable that emits no items to the Observer and immediately\n * emits a complete notification.\n *\n * Just emits 'complete', and nothing else.\n *\n * ![](empty.png)\n *\n * A simple Observable that only emits the complete notification. It can be used\n * for composing with other Observables, such as in a {@link mergeMap}.\n *\n * ## Examples\n *\n * Log complete notification\n *\n * ```ts\n * import { EMPTY } from 'rxjs';\n *\n * EMPTY.subscribe({\n * next: () => console.log('Next'),\n * complete: () => console.log('Complete!')\n * });\n *\n * // Outputs\n * // Complete!\n * ```\n *\n * Emit the number 7, then complete\n *\n * ```ts\n * import { EMPTY, startWith } from 'rxjs';\n *\n * const result = EMPTY.pipe(startWith(7));\n * result.subscribe(x => console.log(x));\n *\n * // Outputs\n * // 7\n * ```\n *\n * Map and flatten only odd numbers to the sequence `'a'`, `'b'`, `'c'`\n *\n * ```ts\n * import { interval, mergeMap, of, EMPTY } from 'rxjs';\n *\n * const interval$ = interval(1000);\n * const result = interval$.pipe(\n * mergeMap(x => x % 2 === 1 ? of('a', 'b', 'c') : EMPTY),\n * );\n * result.subscribe(x => console.log(x));\n *\n * // Results in the following to the console:\n * // x is equal to the count on the interval, e.g. (0, 1, 2, 3, ...)\n * // x will occur every 1000ms\n * // if x % 2 is equal to 1, print a, b, c (each on its own)\n * // if x % 2 is not equal to 1, nothing will be output\n * ```\n *\n * @see {@link Observable}\n * @see {@link NEVER}\n * @see {@link of}\n * @see {@link throwError}\n */\nexport const EMPTY = new Observable((subscriber) => subscriber.complete());\n\n/**\n * @param scheduler A {@link SchedulerLike} to use for scheduling\n * the emission of the complete notification.\n * @deprecated Replaced with the {@link EMPTY} constant or {@link scheduled} (e.g. `scheduled([], scheduler)`). Will be removed in v8.\n */\nexport function empty(scheduler?: SchedulerLike) {\n return scheduler ? emptyScheduled(scheduler) : EMPTY;\n}\n\nfunction emptyScheduled(scheduler: SchedulerLike) {\n return new Observable((subscriber) => scheduler.schedule(() => subscriber.complete()));\n}\n", "import { SchedulerLike } from '../types';\nimport { isFunction } from './isFunction';\n\nexport function isScheduler(value: any): value is SchedulerLike {\n return value && isFunction(value.schedule);\n}\n", "import { SchedulerLike } from '../types';\nimport { isFunction } from './isFunction';\nimport { isScheduler } from './isScheduler';\n\nfunction last(arr: T[]): T | undefined {\n return arr[arr.length - 1];\n}\n\nexport function popResultSelector(args: any[]): ((...args: unknown[]) => unknown) | undefined {\n return isFunction(last(args)) ? args.pop() : undefined;\n}\n\nexport function popScheduler(args: any[]): SchedulerLike | undefined {\n return isScheduler(last(args)) ? args.pop() : undefined;\n}\n\nexport function popNumber(args: any[], defaultValue: number): number {\n return typeof last(args) === 'number' ? args.pop()! : defaultValue;\n}\n", "export const isArrayLike = ((x: any): x is ArrayLike => x && typeof x.length === 'number' && typeof x !== 'function');", "import { isFunction } from \"./isFunction\";\n\n/**\n * Tests to see if the object is \"thennable\".\n * @param value the object to test\n */\nexport function isPromise(value: any): value is PromiseLike {\n return isFunction(value?.then);\n}\n", "import { InteropObservable } from '../types';\nimport { observable as Symbol_observable } from '../symbol/observable';\nimport { isFunction } from './isFunction';\n\n/** Identifies an input as being Observable (but not necessary an Rx Observable) */\nexport function isInteropObservable(input: any): input is InteropObservable {\n return isFunction(input[Symbol_observable]);\n}\n", "import { isFunction } from './isFunction';\n\nexport function isAsyncIterable(obj: any): obj is AsyncIterable {\n return Symbol.asyncIterator && isFunction(obj?.[Symbol.asyncIterator]);\n}\n", "/**\n * Creates the TypeError to throw if an invalid object is passed to `from` or `scheduled`.\n * @param input The object that was passed.\n */\nexport function createInvalidObservableTypeError(input: any) {\n // TODO: We should create error codes that can be looked up, so this can be less verbose.\n return new TypeError(\n `You provided ${\n input !== null && typeof input === 'object' ? 'an invalid object' : `'${input}'`\n } where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.`\n );\n}\n", "export function getSymbolIterator(): symbol {\n if (typeof Symbol !== 'function' || !Symbol.iterator) {\n return '@@iterator' as any;\n }\n\n return Symbol.iterator;\n}\n\nexport const iterator = getSymbolIterator();\n", "import { iterator as Symbol_iterator } from '../symbol/iterator';\nimport { isFunction } from './isFunction';\n\n/** Identifies an input as being an Iterable */\nexport function isIterable(input: any): input is Iterable {\n return isFunction(input?.[Symbol_iterator]);\n}\n", "import { ReadableStreamLike } from '../types';\nimport { isFunction } from './isFunction';\n\nexport async function* readableStreamLikeToAsyncGenerator(readableStream: ReadableStreamLike): AsyncGenerator {\n const reader = readableStream.getReader();\n try {\n while (true) {\n const { value, done } = await reader.read();\n if (done) {\n return;\n }\n yield value!;\n }\n } finally {\n reader.releaseLock();\n }\n}\n\nexport function isReadableStreamLike(obj: any): obj is ReadableStreamLike {\n // We don't want to use instanceof checks because they would return\n // false for instances from another Realm, like an