diff --git a/data/communities.json b/data/communities.json
index b79eda54..628f2854 100644
--- a/data/communities.json
+++ b/data/communities.json
@@ -4,8 +4,9 @@
"metadata": {
"owner": "directory-stewards",
"migrated_at": "2026-07-19",
+ "last_reviewed": "2026-08-29",
"source_revision": "a47dfbc",
- "review_by": "2027-01-15",
+ "review_by": "2027-02-28",
"update_route": "../submit-idea.md"
},
"national_defaults": {
@@ -149,8 +150,8 @@
"type": "telegram",
"label": "Alberta topic in MeshCore Canada",
"url": "https://t.me/MeshCoreCAN",
- "health": "needs-review",
- "last_checked": null
+ "health": "verified",
+ "last_checked": "2026-08-29"
}
],
"communities": [
@@ -192,12 +193,12 @@
"label": "Ridgeline network map and tools",
"url": "https://ridgeline.ve7kod.ca/about",
"health": "verified",
- "last_checked": "2026-08-11"
+ "last_checked": "2026-08-29"
}
],
"owner": null,
- "verified_at": "2026-08-11",
- "verify_by": "2027-02-11",
+ "verified_at": "2026-08-29",
+ "verify_by": "2027-02-28",
"canonical_route": "/provinces/british-columbia/#community-bc-mesh"
},
{
@@ -227,13 +228,13 @@
"type": "website",
"label": "Salish Mesh website",
"url": "https://salishmesh.net/",
- "health": "needs-review",
- "last_checked": null
+ "health": "verified",
+ "last_checked": "2026-08-29"
}
],
"owner": null,
- "verified_at": null,
- "verify_by": null,
+ "verified_at": "2026-08-29",
+ "verify_by": "2027-02-28",
"canonical_route": "/provinces/british-columbia/#community-salish-mesh"
},
{
@@ -269,62 +270,62 @@
"type": "website",
"label": "AlbertaMesh.ca",
"url": "https://albertamesh.ca/",
- "health": "needs-review",
- "last_checked": null
+ "health": "verified",
+ "last_checked": "2026-08-29"
},
{
"type": "discord",
"label": "Alberta MeshCore Discord",
"url": "https://discord.gg/CznDhsRWnJ",
- "health": "needs-review",
- "last_checked": null
+ "health": "verified",
+ "last_checked": "2026-08-29"
},
{
"type": "website",
"label": "Airdrie regional page",
"url": "https://albertamesh.ca/airdrie/",
- "health": "needs-review",
- "last_checked": null
+ "health": "verified",
+ "last_checked": "2026-08-29"
},
{
"type": "website",
"label": "Calgary regional page",
"url": "https://albertamesh.ca/calgary/",
- "health": "needs-review",
- "last_checked": null
+ "health": "verified",
+ "last_checked": "2026-08-29"
},
{
"type": "website",
"label": "Edmonton regional page",
"url": "https://albertamesh.ca/edmonton/",
- "health": "needs-review",
- "last_checked": null
+ "health": "verified",
+ "last_checked": "2026-08-29"
},
{
"type": "website",
"label": "Lethbridge regional page",
"url": "https://albertamesh.ca/lethbridge/",
- "health": "needs-review",
- "last_checked": null
+ "health": "verified",
+ "last_checked": "2026-08-29"
},
{
"type": "website",
"label": "Why MeshCore?",
"url": "https://albertamesh.ca/why-meshcore/",
- "health": "needs-review",
- "last_checked": null
+ "health": "verified",
+ "last_checked": "2026-08-29"
},
{
"type": "website",
"label": "Monitoring tools",
"url": "https://albertamesh.ca/monitoring-tools/",
- "health": "needs-review",
- "last_checked": null
+ "health": "verified",
+ "last_checked": "2026-08-29"
}
],
"owner": null,
- "verified_at": null,
- "verify_by": null,
+ "verified_at": "2026-08-29",
+ "verify_by": "2027-02-28",
"canonical_route": "/provinces/alberta/#community-alberta-meshcore-networks"
},
{
@@ -357,41 +358,41 @@
"type": "website",
"label": "Airdrie MeshCore Network",
"url": "https://albertamesh.ca/airdrie/",
- "health": "needs-review",
- "last_checked": null
+ "health": "verified",
+ "last_checked": "2026-08-29"
},
{
"type": "website",
"label": "Airdrie configuration guide",
"url": "https://albertamesh.ca/airdrie/configuration/",
- "health": "needs-review",
- "last_checked": null
+ "health": "verified",
+ "last_checked": "2026-08-29"
},
{
"type": "discord",
"label": "Alberta MeshCore Discord",
"url": "https://discord.gg/CznDhsRWnJ",
- "health": "needs-review",
- "last_checked": null
+ "health": "verified",
+ "last_checked": "2026-08-29"
},
{
"type": "meshmapper",
"label": "Airdrie network map",
"url": "https://yyc.meshmapper.net/?lat=51.28120&lon=-113.99718&zoom=13.43",
- "health": "needs-review",
- "last_checked": null
+ "health": "verified",
+ "last_checked": "2026-08-29"
},
{
"type": "website",
"label": "WAeV live map",
"url": "https://waev.app/#/live-map/@51.28107,-113.99966,14.47z",
- "health": "needs-review",
- "last_checked": null
+ "health": "verified",
+ "last_checked": "2026-08-29"
}
],
"owner": null,
- "verified_at": null,
- "verify_by": null,
+ "verified_at": "2026-08-29",
+ "verify_by": "2027-02-28",
"canonical_route": "/provinces/alberta/#community-airdrie-meshcore-network"
},
{
@@ -422,20 +423,20 @@
"type": "telegram",
"label": "Calgary topic in Mesh Alberta",
"url": "https://t.me/meshtAlta",
- "health": "needs-review",
- "last_checked": null
+ "health": "verified",
+ "last_checked": "2026-08-29"
},
{
"type": "discord",
"label": "Canada - Calgary, Alberta & Area regional channel in the MeshCore Discord",
"url": null,
"health": "needs-review",
- "last_checked": null
+ "last_checked": "2026-08-29"
}
],
"owner": null,
- "verified_at": null,
- "verify_by": null,
+ "verified_at": "2026-08-29",
+ "verify_by": "2027-02-28",
"canonical_route": "/provinces/alberta/#community-calgary-area-meshcore"
},
{
@@ -450,7 +451,7 @@
"aliases": [
"AlbertaMesh Calgary",
"YYC",
- "meshcorecalgary.ca"
+ "Calgary MeshCore"
],
"status": "active",
"languages": [],
@@ -464,52 +465,45 @@
"overrides": {}
},
"contacts": [
- {
- "type": "website",
- "label": "Calgary MeshCore Network",
- "url": "https://meshcorecalgary.ca/",
- "health": "needs-review",
- "last_checked": null
- },
{
"type": "website",
"label": "Calgary community guide",
"url": "https://albertamesh.ca/calgary/",
- "health": "needs-review",
- "last_checked": null
+ "health": "verified",
+ "last_checked": "2026-08-29"
},
{
"type": "discord",
"label": "Alberta MeshCore Discord",
"url": "https://discord.gg/CznDhsRWnJ",
- "health": "needs-review",
- "last_checked": null
+ "health": "verified",
+ "last_checked": "2026-08-29"
},
{
"type": "meshmapper",
"label": "Calgary network map",
"url": "https://yyc.meshmapper.net/?lat=51.01674&lon=-114.00149&zoom=11.00",
- "health": "needs-review",
- "last_checked": null
+ "health": "verified",
+ "last_checked": "2026-08-29"
},
{
"type": "website",
"label": "CoreScope YYC",
"url": "https://corescopeyyc.meshmonitoring.com/#/live",
- "health": "needs-review",
- "last_checked": null
+ "health": "verified",
+ "last_checked": "2026-08-29"
},
{
"type": "website",
"label": "Recommended Calgary RX channels",
"url": "https://albertamesh.ca/calgary/#rx-channels",
- "health": "needs-review",
- "last_checked": null
+ "health": "verified",
+ "last_checked": "2026-08-29"
}
],
"owner": null,
- "verified_at": null,
- "verify_by": null,
+ "verified_at": "2026-08-29",
+ "verify_by": "2027-02-28",
"canonical_route": "/provinces/alberta/#community-calgary-meshcore-network"
},
{
@@ -541,34 +535,34 @@
"type": "website",
"label": "Edmonton on AlbertaMesh.ca",
"url": "https://albertamesh.ca/edmonton/",
- "health": "needs-review",
- "last_checked": null
+ "health": "verified",
+ "last_checked": "2026-08-29"
},
{
"type": "website",
"label": "Edmonton configuration guide",
"url": "https://albertamesh.ca/edmonton/configuration/",
- "health": "needs-review",
- "last_checked": null
+ "health": "verified",
+ "last_checked": "2026-08-29"
},
{
"type": "discord",
"label": "Alberta MeshCore Discord",
"url": "https://discord.gg/CznDhsRWnJ",
- "health": "needs-review",
- "last_checked": null
+ "health": "verified",
+ "last_checked": "2026-08-29"
},
{
"type": "meshmapper",
"label": "Edmonton network map",
"url": "https://yeg.meshmapper.net/?lat=53.45752&lon=-113.58320&zoom=10.03",
- "health": "needs-review",
- "last_checked": null
+ "health": "verified",
+ "last_checked": "2026-08-29"
}
],
"owner": null,
- "verified_at": null,
- "verify_by": null,
+ "verified_at": "2026-08-29",
+ "verify_by": "2027-02-28",
"canonical_route": "/provinces/alberta/#community-edmonton-meshcore-network"
},
{
@@ -601,34 +595,34 @@
"type": "website",
"label": "Getting started",
"url": "https://yegmesh.ca/p/getting-started",
- "health": "needs-review",
- "last_checked": null
+ "health": "verified",
+ "last_checked": "2026-08-29"
},
{
"type": "website",
"label": "MeshCore defaults",
"url": "https://yegmesh.ca/p/meshcore-defaults",
- "health": "needs-review",
- "last_checked": null
+ "health": "verified",
+ "last_checked": "2026-08-29"
},
{
"type": "discord",
"label": "Alberta MeshCore Discord",
"url": "https://discord.gg/CznDhsRWnJ",
- "health": "needs-review",
- "last_checked": null
+ "health": "verified",
+ "last_checked": "2026-08-29"
},
{
"type": "meshmapper",
"label": "Edmonton network map",
"url": "https://yeg.meshmapper.net/?lat=53.45752&lon=-113.58320&zoom=10.03",
- "health": "needs-review",
- "last_checked": null
+ "health": "verified",
+ "last_checked": "2026-08-29"
}
],
"owner": null,
- "verified_at": null,
- "verify_by": null,
+ "verified_at": "2026-08-29",
+ "verify_by": "2027-02-28",
"canonical_route": "/provinces/alberta/#community-yegmesh-ca"
},
{
@@ -659,62 +653,62 @@
"type": "website",
"label": "YQLMesh website",
"url": "https://www.yqlmesh.com/",
- "health": "needs-review",
- "last_checked": null
+ "health": "verified",
+ "last_checked": "2026-08-29"
},
{
"type": "website",
"label": "Lethbridge regional page",
"url": "https://albertamesh.ca/lethbridge/",
- "health": "needs-review",
- "last_checked": null
+ "health": "verified",
+ "last_checked": "2026-08-29"
},
{
"type": "discord",
"label": "YQLMesh Discord",
"url": "https://discord.gg/cFY9GSR37W",
- "health": "needs-review",
- "last_checked": null
+ "health": "verified",
+ "last_checked": "2026-08-29"
},
{
"type": "meshmapper",
"label": "Lethbridge network map",
"url": "https://yql.meshmapper.net/?lat=49.69091&lon=-112.86356&zoom=12.14",
- "health": "needs-review",
- "last_checked": null
+ "health": "verified",
+ "last_checked": "2026-08-29"
},
{
"type": "facebook",
"label": "YQLMesh Facebook",
"url": "https://facebook.com/groups/YQLMesh",
"health": "needs-review",
- "last_checked": null
+ "last_checked": "2026-08-29"
},
{
"type": "instagram",
"label": "YQLMesh Instagram",
"url": "https://instagram.com/YQLMesh",
- "health": "needs-review",
- "last_checked": null
+ "health": "verified",
+ "last_checked": "2026-08-29"
},
{
"type": "x",
"label": "YQLMesh on X",
"url": "https://x.com/YQLMesh",
- "health": "needs-review",
- "last_checked": null
+ "health": "verified",
+ "last_checked": "2026-08-29"
},
{
"type": "reddit",
"label": "YQLMesh subreddit",
"url": "https://www.reddit.com/r/YQLMesh",
- "health": "needs-review",
- "last_checked": null
+ "health": "verified",
+ "last_checked": "2026-08-29"
}
],
"owner": null,
- "verified_at": null,
- "verify_by": null,
+ "verified_at": "2026-08-29",
+ "verify_by": "2027-02-28",
"canonical_route": "/provinces/alberta/#community-yqlmesh"
},
{
@@ -747,19 +741,19 @@
"label": "Canada - Southern Alberta regional channel in the MeshCore Discord",
"url": null,
"health": "needs-review",
- "last_checked": null
+ "last_checked": "2026-08-29"
},
{
"type": "telegram",
"label": "Cardston topic in Mesh Alberta",
"url": "https://t.me/meshtAlta",
- "health": "needs-review",
- "last_checked": null
+ "health": "verified",
+ "last_checked": "2026-08-29"
}
],
"owner": null,
- "verified_at": null,
- "verify_by": null,
+ "verified_at": "2026-08-29",
+ "verify_by": "2027-02-28",
"canonical_route": "/provinces/alberta/#community-southern-alberta"
},
{
@@ -789,20 +783,20 @@
"type": "discord",
"label": "YYC MeshCore Discord",
"url": "https://discord.gg/CznDhsRWnJ",
- "health": "needs-review",
- "last_checked": null
+ "health": "verified",
+ "last_checked": "2026-08-29"
},
{
"type": "website",
"label": "MeshMonitoring",
"url": "https://meshmonitoring.com/",
- "health": "needs-review",
- "last_checked": null
+ "health": "verified",
+ "last_checked": "2026-08-29"
}
],
"owner": null,
- "verified_at": null,
- "verify_by": null,
+ "verified_at": "2026-08-29",
+ "verify_by": "2027-02-28",
"canonical_route": "/provinces/alberta/#community-yyc-meshcore-discord"
},
{
@@ -827,16 +821,16 @@
},
"contacts": [
{
- "type": "discord",
- "label": "StoonMesh Discord",
- "url": "https://discord.gg/7yGnJuMGkG",
- "health": "needs-review",
- "last_checked": null
+ "type": "telegram",
+ "label": "Mesh Saskatchewan Telegram",
+ "url": "https://t.me/MeshtSaska",
+ "health": "verified",
+ "last_checked": "2026-08-29"
}
],
"owner": null,
- "verified_at": null,
- "verify_by": null,
+ "verified_at": "2026-08-29",
+ "verify_by": "2027-02-28",
"canonical_route": "/provinces/saskatchewan/#community-stoonmesh"
},
{
@@ -864,8 +858,8 @@
},
"contacts": [],
"owner": null,
- "verified_at": null,
- "verify_by": null,
+ "verified_at": "2026-08-29",
+ "verify_by": "2027-02-28",
"canonical_route": "/provinces/saskatchewan/#community-yqrmesh"
},
{
@@ -896,20 +890,20 @@
"type": "discord",
"label": "Greater Ottawa Mesh Enthusiasts Discord",
"url": "https://discord.gg/WSyNd8SfNr",
- "health": "needs-review",
- "last_checked": null
+ "health": "verified",
+ "last_checked": "2026-08-29"
},
{
"type": "website",
"label": "Ottawa Mesh website",
"url": "https://ottawamesh.ca/",
- "health": "needs-review",
- "last_checked": null
+ "health": "verified",
+ "last_checked": "2026-08-29"
}
],
"owner": null,
- "verified_at": null,
- "verify_by": null,
+ "verified_at": "2026-08-29",
+ "verify_by": "2027-02-28",
"canonical_route": "/provinces/ontario/#community-greater-ottawa-mesh-enthusiasts"
},
{
@@ -938,13 +932,13 @@
"type": "discord",
"label": "GTA+-Lora-Meshes Discord",
"url": "https://discord.gg/wSHbeb86r4",
- "health": "needs-review",
- "last_checked": null
+ "health": "verified",
+ "last_checked": "2026-08-29"
}
],
"owner": null,
- "verified_at": null,
- "verify_by": null,
+ "verified_at": "2026-08-29",
+ "verify_by": "2027-02-28",
"canonical_route": "/provinces/ontario/#community-gta-lora-meshes"
},
{
@@ -976,20 +970,20 @@
"type": "discord",
"label": "Quinte Mesh Network Discord",
"url": "https://discord.gg/V5esJEP67X",
- "health": "needs-review",
- "last_checked": null
+ "health": "verified",
+ "last_checked": "2026-08-29"
},
{
"type": "website",
"label": "Quinte Mesh Network website",
"url": "https://quintemesh.ca/",
- "health": "needs-review",
- "last_checked": null
+ "health": "verified",
+ "last_checked": "2026-08-29"
}
],
"owner": null,
- "verified_at": null,
- "verify_by": null,
+ "verified_at": "2026-08-29",
+ "verify_by": "2027-02-28",
"canonical_route": "/provinces/ontario/#community-quinte-mesh-network"
},
{
@@ -1023,12 +1017,12 @@
"label": "Réseau MESH de Charlevoix",
"url": "https://chxmesh.ca/",
"health": "verified",
- "last_checked": "2026-08-08"
+ "last_checked": "2026-08-29"
}
],
"owner": "pifane",
- "verified_at": "2026-07-31",
- "verify_by": "2027-01-31",
+ "verified_at": "2026-08-29",
+ "verify_by": "2027-02-28",
"canonical_route": "/provinces/quebec/#community-charlevoix-yml"
},
{
@@ -1053,16 +1047,16 @@
},
"contacts": [
{
- "type": "website",
- "label": "Mesh Quebec website",
- "url": "https://qcmesh.net",
- "health": "needs-review",
- "last_checked": null
+ "type": "telegram",
+ "label": "Mesh Quebec Telegram",
+ "url": "https://t.me/meshtQuebec",
+ "health": "verified",
+ "last_checked": "2026-08-29"
}
],
"owner": null,
- "verified_at": null,
- "verify_by": null,
+ "verified_at": "2026-08-29",
+ "verify_by": "2027-02-28",
"canonical_route": "/provinces/quebec/#community-mesh-quebec"
},
{
@@ -1093,13 +1087,13 @@
"type": "website",
"label": "Montreal Mesh website",
"url": "https://www.montrealmesh.ca",
- "health": "needs-review",
- "last_checked": null
+ "health": "verified",
+ "last_checked": "2026-08-29"
}
],
"owner": null,
- "verified_at": null,
- "verify_by": null,
+ "verified_at": "2026-08-29",
+ "verify_by": "2027-02-28",
"canonical_route": "/provinces/quebec/#community-montreal-mesh"
},
{
@@ -1130,13 +1124,13 @@
"type": "discord",
"label": "Réseau Mesh de la Capitale YQB Discord",
"url": "https://discord.gg/UhGjTF2MfA",
- "health": "needs-review",
- "last_checked": null
+ "health": "verified",
+ "last_checked": "2026-08-29"
}
],
"owner": null,
- "verified_at": null,
- "verify_by": null,
+ "verified_at": "2026-08-29",
+ "verify_by": "2027-02-28",
"canonical_route": "/provinces/quebec/#community-reseau-mesh-capitale-yqb"
},
{
@@ -1164,31 +1158,24 @@
"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
+ "last_checked": "2026-08-29"
},
{
"type": "meshmapper",
"label": "Réseau Mesh du Saguenay Lac st-Jean YTF MeshMapper",
"url": "https://ytf.meshmapper.net/",
- "health": "needs-review",
- "last_checked": null
+ "health": "verified",
+ "last_checked": "2026-08-29"
}
],
"owner": null,
- "verified_at": null,
- "verify_by": null,
+ "verified_at": "2026-08-29",
+ "verify_by": "2027-02-28",
"canonical_route": "/provinces/quebec/#community-reseau-mesh-saguenay-lac-saint-jean-ytf"
},
{
@@ -1218,13 +1205,13 @@
"type": "website",
"label": "Réseau Libre website",
"url": "https://lora.reseaulibre.ca/",
- "health": "needs-review",
- "last_checked": null
+ "health": "verified",
+ "last_checked": "2026-08-29"
}
],
"owner": null,
- "verified_at": null,
- "verify_by": null,
+ "verified_at": "2026-08-29",
+ "verify_by": "2027-02-28",
"canonical_route": "/provinces/quebec/#community-reseau-libre"
},
{
@@ -1257,12 +1244,12 @@
"label": "MESHCORE Saint John, N.B.",
"url": "https://www.facebook.com/groups/613466831163684",
"health": "needs-review",
- "last_checked": null
+ "last_checked": "2026-08-29"
}
],
"owner": null,
- "verified_at": null,
- "verify_by": null,
+ "verified_at": "2026-08-29",
+ "verify_by": "2027-02-28",
"canonical_route": "/provinces/new-brunswick/#community-southern-new-brunswick"
},
{
@@ -1290,13 +1277,13 @@
"type": "website",
"label": "Lunenburg County Mesh website",
"url": "http://www.lunenburgcountymesh.ca",
- "health": "needs-review",
- "last_checked": null
+ "health": "verified",
+ "last_checked": "2026-08-29"
}
],
"owner": null,
- "verified_at": null,
- "verify_by": null,
+ "verified_at": "2026-08-29",
+ "verify_by": "2027-02-28",
"canonical_route": "/provinces/nova-scotia/#community-lunenburg-county-mesh"
}
]
diff --git a/docs/assets/MeshCore-Canada-Favicon.png b/docs/assets/MeshCore-Canada-Favicon.png
deleted file mode 100644
index f4bfec67..00000000
Binary files a/docs/assets/MeshCore-Canada-Favicon.png and /dev/null differ
diff --git a/docs/assets/MeshCore-Canada-Favicon.svg b/docs/assets/MeshCore-Canada-Favicon.svg
new file mode 100644
index 00000000..f4b422fd
--- /dev/null
+++ b/docs/assets/MeshCore-Canada-Favicon.svg
@@ -0,0 +1,13 @@
+
+ MeshCore Canada favicon
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/assets/MeshCore-Canada-Mark.svg b/docs/assets/MeshCore-Canada-Mark.svg
new file mode 100644
index 00000000..a292556e
--- /dev/null
+++ b/docs/assets/MeshCore-Canada-Mark.svg
@@ -0,0 +1,13 @@
+
+ MeshCore Canada
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/assets/javascripts/i18n-runtime.js b/docs/assets/javascripts/i18n-runtime.js
index 125dc5d0..b785be7a 100644
--- a/docs/assets/javascripts/i18n-runtime.js
+++ b/docs/assets/javascripts/i18n-runtime.js
@@ -37,6 +37,7 @@
"2. Choose an area": "2. Choisir une zone",
"Province or territory": "Province ou territoire",
"Loading editor…": "Chargement de l’éditeur…",
+ "Choose a proposal type to load the editor.": "Choisissez un type de proposition pour charger l’éditeur.",
"Choose a proposal type to begin.": "Choisissez un type de proposition pour commencer.",
"Discard saved draft": "Supprimer le brouillon enregistré",
"3. Build the proposal": "3. Préparer la proposition",
@@ -107,6 +108,7 @@
"Website": "Site Web",
"Anti-spam check": "Vérification antipourriel",
"Loading check…": "Chargement de la vérification…",
+ "The anti-spam check loads when you reach this step.": "La vérification antipourriel se charge lorsque vous arrivez à cette étape.",
"Retry check": "Réessayer la vérification",
"Submit for review": "Soumettre aux fins d’examen",
"Download proposal": "Télécharger la proposition",
diff --git a/docs/config/editor/app.js b/docs/config/editor/app.js
index d62b2cfa..08132ba9 100644
--- a/docs/config/editor/app.js
+++ b/docs/config/editor/app.js
@@ -164,6 +164,7 @@ import {
after: document.getElementById("after-view"),
changeCount: document.getElementById("change-count"),
textSummary: document.getElementById("text-summary"),
+ reviewPanel: document.getElementById("review-panel"),
changesTableBody: document.getElementById("changes-table-body"),
changesTableNote: document.getElementById("changes-table-note"),
readinessList: document.getElementById("readiness-list"),
@@ -187,7 +188,6 @@ import {
var state = {
catalog: null,
- partition: null,
membership: new Map(),
baseMembershipSha256: "",
features: [],
@@ -235,12 +235,6 @@ import {
attribution: '© OpenStreetMap contributors'
}).addTo(map);
- var partitionLayer = L.geoJSON(null, {
- interactive: false,
- style: function () {
- return { color: "#8d93ae", weight: 1, opacity: 0.4, fillOpacity: 0.025 };
- }
- }).addTo(map);
var cellLayer = L.geoJSON(null, {
style: styleFeature,
onEachFeature: wireCell
@@ -1271,7 +1265,7 @@ import {
async function loadProvince(pruid) {
if (!pruid) {
- return;
+ return false;
}
if (state.loadController) {
state.loadController.abort();
@@ -1318,15 +1312,15 @@ import {
if (cellLayer.getBounds().isValid()) {
map.fitBounds(cellLayer.getBounds(), { padding: [18, 18] });
}
- partitionLayer.bringToBack();
setStatus(
state.features.length.toLocaleString() + " census cells loaded.",
"success"
);
+ return true;
} catch (error) {
if (error.name === "AbortError") {
state.restoringDraft = false;
- return;
+ return false;
}
state.membership.clear();
state.features = [];
@@ -1335,6 +1329,7 @@ import {
cellLayer.clearLayers();
state.restoringDraft = false;
setStatus(error.message, "error");
+ return false;
}
}
@@ -1519,7 +1514,7 @@ import {
}
async function initialiseSubmission() {
- if (state.submissionInitialising) return;
+ if (state.submissionInitialising || state.turnstileWidgetId !== null) return;
state.submissionInitialising = true;
elements.antiSpamRetry.hidden = true;
setAntiSpamStatus("Loading check…", "");
@@ -1703,28 +1698,67 @@ import {
await loadProvince(nextProvince);
}
- async function initialise() {
+ var editorInitialisePromise = null;
+
+ async function loadEditor() {
+ document.documentElement.dataset.editorState = "loading";
setStatus("Loading editor…", "");
+ elements.proposalTypes.forEach(function (input) { input.disabled = true; });
try {
state.catalog = await (await fetchOk("canada-regions.json")).json();
await loadMembershipCsv();
var manifest = null;
try { manifest = await (await fetchOk("cells/manifest.json")).json(); } catch (_e) {}
populateProvinceOptions(manifest);
- try {
- var partition = await (await fetchOk("canada-region-partition.geojson")).json();
- partitionLayer.clearLayers();
- partitionLayer.addData(partition);
- } catch (_e) {}
- await loadProvince(elements.province.value);
+ if (!await loadProvince(elements.province.value)) {
+ document.documentElement.dataset.editorState = "error";
+ editorInitialisePromise = null;
+ return false;
+ }
+ document.documentElement.dataset.editorState = "ready";
+ return true;
} catch (error) {
setStatus(error.message, "error");
+ document.documentElement.dataset.editorState = "error";
+ editorInitialisePromise = null;
+ return false;
+ } finally {
+ elements.proposalTypes.forEach(function (input) { input.disabled = false; });
+ }
+ }
+
+ function initialise() {
+ if (!editorInitialisePromise) editorInitialisePromise = loadEditor();
+ return editorInitialisePromise;
+ }
+
+ var submissionTriggersReady = false;
+
+ function prepareSubmissionWhenNeeded() {
+ if (submissionTriggersReady) return;
+ submissionTriggersReady = true;
+ var start = function () { void initialiseSubmission(); };
+ elements.reviewPanel.addEventListener("focusin", start, { once: true });
+ elements.reviewPanel.addEventListener("pointerdown", start, { once: true });
+ if ("IntersectionObserver" in window) {
+ var observer = new IntersectionObserver(function (entries) {
+ if (!entries.some(function (entry) { return entry.isIntersecting; })) return;
+ observer.disconnect();
+ start();
+ }, { rootMargin: "200px" });
+ observer.observe(elements.reviewPanel);
}
}
elements.proposalTypes.forEach(function (input) {
- input.addEventListener("change", function () {
- if (input.checked) changeProposalType(input.value);
+ input.addEventListener("change", async function () {
+ if (!input.checked) return;
+ if (!await initialise()) {
+ input.checked = false;
+ return;
+ }
+ await changeProposalType(input.value);
+ prepareSubmissionWhenNeeded();
});
});
elements.province.addEventListener("change", function () {
@@ -1863,6 +1897,4 @@ import {
});
window.addEventListener("beforeunload", flushDraftSave);
- initialise();
- initialiseSubmission();
}());
diff --git a/docs/config/editor/index.html b/docs/config/editor/index.html
index 38d3f6a5..85f04102 100644
--- a/docs/config/editor/index.html
+++ b/docs/config/editor/index.html
@@ -1,5 +1,5 @@
-
+
@@ -11,8 +11,8 @@
-
-
+
+
Skip the map and choose a municipality
@@ -64,7 +64,7 @@ 1. Choose proposal type
2. Choose an area
Province or territory
- Loading editor…
+ Choose a proposal type to load the editor.
Choose a proposal type to begin.
Discard saved draft
@@ -134,7 +134,7 @@ Map inspection
-
+
4. Review
0 changes
@@ -179,7 +179,7 @@ Ready to submit?
- Loading check…
+ The anti-spam check loads when you reach this step.
Retry check
diff --git a/docs/provinces/alberta.fr.md b/docs/provinces/alberta.fr.md
index 39419b8a..0f445769 100644
--- a/docs/provinces/alberta.fr.md
+++ b/docs/provinces/alberta.fr.md
@@ -8,8 +8,8 @@ task: browse-community-directory
scope: canada-baseline
status: draft
owner: directory-stewards
-last_reviewed: 2026-07-19
-review_by: 2027-01-15
+last_reviewed: 2026-08-29
+review_by: 2027-02-28
difficulty: beginner
estimated_time: 2-5 minutes
destructive: false
@@ -40,7 +40,7 @@ sauf si une fiche indique des réglages locaux différents.
Réglages
Réglages par défaut du Canada
Dernière vérification
-Pas encore vérifiée
+2026-08-29
Coordonnées
@@ -69,7 +69,7 @@ sauf si une fiche indique des réglages locaux différents.
Réglages
Réglages par défaut du Canada
Dernière vérification
-Pas encore vérifiée
+2026-08-29
Coordonnées
@@ -94,7 +94,7 @@ sauf si une fiche indique des réglages locaux différents.
Réglages
Réglages par défaut du Canada
Dernière vérification
-Pas encore vérifiée
+2026-08-29
Coordonnées
@@ -117,11 +117,10 @@ sauf si une fiche indique des réglages locaux différents.
Réglages
Réglages par défaut du Canada
Dernière vérification
-Pas encore vérifiée
+2026-08-29
Coordonnées
@@ -144,7 +143,7 @@ sauf si une fiche indique des réglages locaux différents.
Réglages
Réglages par défaut du Canada
Dernière vérification
-Pas encore vérifiée
+2026-08-29
Coordonnées
@@ -169,7 +168,7 @@ sauf si une fiche indique des réglages locaux différents.
Réglages
Réglages par défaut du Canada
Dernière vérification
-Pas encore vérifiée
+2026-08-29
Coordonnées
@@ -194,7 +193,7 @@ sauf si une fiche indique des réglages locaux différents.
Réglages
Réglages par défaut du Canada
Dernière vérification
-Pas encore vérifiée
+2026-08-29
Coordonnées
@@ -222,7 +221,7 @@ sauf si une fiche indique des réglages locaux différents.
Réglages
Réglages par défaut du Canada
Dernière vérification
-Pas encore vérifiée
+2026-08-29
Coordonnées
@@ -244,7 +243,7 @@ sauf si une fiche indique des réglages locaux différents.
Réglages
Réglages par défaut du Canada
Dernière vérification
-Pas encore vérifiée
+2026-08-29
Coordonnées
@@ -262,7 +261,7 @@ sauf si une fiche indique des réglages locaux différents.
## Ajouter ou mettre à jour une fiche
diff --git a/docs/provinces/alberta.md b/docs/provinces/alberta.md
index 6263de8d..48a696b4 100644
--- a/docs/provinces/alberta.md
+++ b/docs/provinces/alberta.md
@@ -8,8 +8,8 @@ task: browse-community-directory
scope: canada-baseline
status: draft
owner: directory-stewards
-last_reviewed: 2026-07-19
-review_by: 2027-01-15
+last_reviewed: 2026-08-29
+review_by: 2027-02-28
difficulty: beginner
estimated_time: 2-5 minutes
destructive: false
@@ -40,7 +40,7 @@ card lists different local settings.
Settings
Uses the Canada defaults
Last verified
-Not yet verified
+2026-08-29
Contacts
@@ -69,7 +69,7 @@ card lists different local settings.
Settings
Uses the Canada defaults
Last verified
-Not yet verified
+2026-08-29
Contacts
@@ -94,7 +94,7 @@ card lists different local settings.
Settings
Uses the Canada defaults
Last verified
-Not yet verified
+2026-08-29
Contacts
@@ -117,11 +117,10 @@ card lists different local settings.
Settings
Uses the Canada defaults
Last verified
-Not yet verified
+2026-08-29
Contacts
@@ -144,7 +143,7 @@ card lists different local settings.
Settings
Uses the Canada defaults
Last verified
-Not yet verified
+2026-08-29
Contacts
@@ -169,7 +168,7 @@ card lists different local settings.
Settings
Uses the Canada defaults
Last verified
-Not yet verified
+2026-08-29
Contacts
@@ -194,7 +193,7 @@ card lists different local settings.
Settings
Uses the Canada defaults
Last verified
-Not yet verified
+2026-08-29
Contacts
@@ -222,7 +221,7 @@ card lists different local settings.
Settings
Uses the Canada defaults
Last verified
-Not yet verified
+2026-08-29
Contacts
@@ -244,7 +243,7 @@ card lists different local settings.
Settings
Uses the Canada defaults
Last verified
-Not yet verified
+2026-08-29
Contacts
@@ -262,7 +261,7 @@ card lists different local settings.
## Add or update a listing
diff --git a/docs/provinces/british-columbia.fr.md b/docs/provinces/british-columbia.fr.md
index abd6dc34..fab08e78 100644
--- a/docs/provinces/british-columbia.fr.md
+++ b/docs/provinces/british-columbia.fr.md
@@ -8,8 +8,8 @@ task: browse-community-directory
scope: canada-baseline
status: draft
owner: directory-stewards
-last_reviewed: 2026-07-19
-review_by: 2027-01-15
+last_reviewed: 2026-08-29
+review_by: 2027-02-28
difficulty: beginner
estimated_time: 2-5 minutes
destructive: false
@@ -44,7 +44,7 @@ sauf si une fiche indique des réglages locaux différents.
Réglages
Préréglage radio : Custom; Valeurs radio brutes : 910.425 MHz / 62.5 kHz / SF7 / CR5
Dernière vérification
-2026-08-11
+2026-08-29
Coordonnées
diff --git a/docs/provinces/british-columbia.md b/docs/provinces/british-columbia.md
index 770136c8..1ef7f6d0 100644
--- a/docs/provinces/british-columbia.md
+++ b/docs/provinces/british-columbia.md
@@ -8,8 +8,8 @@ task: browse-community-directory
scope: canada-baseline
status: draft
owner: directory-stewards
-last_reviewed: 2026-07-19
-review_by: 2027-01-15
+last_reviewed: 2026-08-29
+review_by: 2027-02-28
difficulty: beginner
estimated_time: 2-5 minutes
destructive: false
@@ -43,7 +43,7 @@ card lists different local settings.
Settings
Radio preset: Custom; Raw radio values: 910.425 MHz / 62.5 kHz / SF7 / CR5
Last verified
-2026-08-11
+2026-08-29
Contacts
diff --git a/docs/provinces/index.fr.md b/docs/provinces/index.fr.md
index d66924cd..9092deb4 100644
--- a/docs/provinces/index.fr.md
+++ b/docs/provinces/index.fr.md
@@ -8,8 +8,8 @@ task: find-community
scope: canada-baseline
status: draft
owner: directory-stewards
-last_reviewed: 2026-07-19
-review_by: 2027-01-15
+last_reviewed: 2026-08-29
+review_by: 2027-02-28
difficulty: beginner
estimated_time: 2-5 minutes
destructive: false
@@ -27,7 +27,7 @@ Recherchez par lieu, province, nom de communauté ou alias courant. La liste
complète fonctionne sans carte, autorisation de localisation ni compte GitHub.
!!! note "Les renseignements sur les communautés peuvent changer"
- 22 fiches sur 24 n’ont pas fait l’objet d’une vérification récente des coordonnées.
+ 0 fiches sur 24 n’ont pas fait l’objet d’une vérification récente des coordonnées.
Confirmez les réglages et les coordonnées importants avant de vous y fier.
@@ -78,7 +78,7 @@ complète fonctionne sans carte, autorisation de localisation ni compte GitHub.
Province ou territoire : Colombie-Britannique
Réglages : Réglages locaux différents — Préréglage radio : Custom; Valeurs radio brutes : 910.425 MHz / 62.5 kHz / SF7 / CR5
-
Dernière vérification : 2026-08-11
+
Dernière vérification : 2026-08-29
@@ -92,7 +92,7 @@ complète fonctionne sans carte, autorisation de localisation ni compte GitHub.
Province ou territoire : Colombie-Britannique
Réglages : Réglages par défaut du Canada
-
Dernière vérification : Pas encore vérifiée
+
Dernière vérification : 2026-08-29
@@ -107,7 +107,7 @@ complète fonctionne sans carte, autorisation de localisation ni compte GitHub.
Province ou territoire : Alberta
Réglages : Réglages par défaut du Canada
-
Dernière vérification : Pas encore vérifiée
+
Dernière vérification : 2026-08-29
Contacts
diff --git a/docs/provinces/ontario.fr.md b/docs/provinces/ontario.fr.md
index bc0eb5e4..3bfdf0c4 100644
--- a/docs/provinces/ontario.fr.md
+++ b/docs/provinces/ontario.fr.md
@@ -8,8 +8,8 @@ task: browse-community-directory
scope: canada-baseline
status: draft
owner: directory-stewards
-last_reviewed: 2026-07-19
-review_by: 2027-01-15
+last_reviewed: 2026-08-29
+review_by: 2027-02-28
difficulty: beginner
estimated_time: 2-5 minutes
destructive: false
@@ -39,7 +39,7 @@ sauf si une fiche indique des réglages locaux différents.
Réglages
Réglages par défaut du Canada
Dernière vérification
-Pas encore vérifiée
+2026-08-29
Coordonnées
@@ -61,14 +61,14 @@ sauf si une fiche indique des réglages locaux différents.
Réglages
Réglages par défaut du Canada
Dernière vérification
-Pas encore vérifiée
+2026-08-29
Coordonnées
@@ -82,7 +82,7 @@ sauf si une fiche indique des réglages locaux différents.
Réglages
Réglages par défaut du Canada
Dernière vérification
-Pas encore vérifiée
+2026-08-29
Coordonnées
diff --git a/docs/provinces/ontario.md b/docs/provinces/ontario.md
index 317b12e5..9786fd4e 100644
--- a/docs/provinces/ontario.md
+++ b/docs/provinces/ontario.md
@@ -8,8 +8,8 @@ task: browse-community-directory
scope: canada-baseline
status: draft
owner: directory-stewards
-last_reviewed: 2026-07-19
-review_by: 2027-01-15
+last_reviewed: 2026-08-29
+review_by: 2027-02-28
difficulty: beginner
estimated_time: 2-5 minutes
destructive: false
@@ -39,7 +39,7 @@ card lists different local settings.
Settings
Uses the Canada defaults
Last verified
-Not yet verified
+2026-08-29
Contacts
@@ -61,14 +61,14 @@ card lists different local settings.
Settings
Uses the Canada defaults
Last verified
-Not yet verified
+2026-08-29
Contacts
@@ -82,7 +82,7 @@ card lists different local settings.
Settings
Uses the Canada defaults
Last verified
-Not yet verified
+2026-08-29
Contacts
diff --git a/docs/provinces/prince-edward-island.fr.md b/docs/provinces/prince-edward-island.fr.md
index 8b87759e..28d7e7e9 100644
--- a/docs/provinces/prince-edward-island.fr.md
+++ b/docs/provinces/prince-edward-island.fr.md
@@ -8,8 +8,8 @@ task: browse-community-directory
scope: canada-baseline
status: draft
owner: directory-stewards
-last_reviewed: 2026-07-19
-review_by: 2027-01-15
+last_reviewed: 2026-08-29
+review_by: 2027-02-28
difficulty: beginner
estimated_time: 2-5 minutes
destructive: false
diff --git a/docs/provinces/prince-edward-island.md b/docs/provinces/prince-edward-island.md
index d0101f88..26fc9472 100644
--- a/docs/provinces/prince-edward-island.md
+++ b/docs/provinces/prince-edward-island.md
@@ -8,8 +8,8 @@ task: browse-community-directory
scope: canada-baseline
status: draft
owner: directory-stewards
-last_reviewed: 2026-07-19
-review_by: 2027-01-15
+last_reviewed: 2026-08-29
+review_by: 2027-02-28
difficulty: beginner
estimated_time: 2-5 minutes
destructive: false
diff --git a/docs/provinces/quebec.fr.md b/docs/provinces/quebec.fr.md
index f795f57d..d71872b3 100644
--- a/docs/provinces/quebec.fr.md
+++ b/docs/provinces/quebec.fr.md
@@ -8,8 +8,8 @@ task: browse-community-directory
scope: canada-baseline
status: draft
owner: directory-stewards
-last_reviewed: 2026-07-19
-review_by: 2027-01-15
+last_reviewed: 2026-08-29
+review_by: 2027-02-28
difficulty: beginner
estimated_time: 2-5 minutes
destructive: false
@@ -39,7 +39,7 @@ sauf si une fiche indique des réglages locaux différents.
Réglages
Réglages par défaut du Canada
Dernière vérification
-2026-07-31
+2026-08-29
Coordonnées
@@ -61,14 +61,14 @@ sauf si une fiche indique des réglages locaux différents.
Réglages
Réglages par défaut du Canada
Dernière vérification
-Pas encore vérifiée
+2026-08-29
Coordonnées
@@ -82,14 +82,14 @@ sauf si une fiche indique des réglages locaux différents.
Réglages
Réglages par défaut du Canada
Dernière vérification
-Pas encore vérifiée
+2026-08-29
Coordonnées
@@ -103,14 +103,14 @@ sauf si une fiche indique des réglages locaux différents.
Réglages
Réglages par défaut du Canada
Dernière vérification
-Pas encore vérifiée
+2026-08-29
Coordonnées
@@ -124,16 +124,15 @@ sauf si une fiche indique des réglages locaux différents.
Réglages
Réglages par défaut du Canada
Dernière vérification
-Pas encore vérifiée
+2026-08-29
Coordonnées
@@ -147,14 +146,14 @@ sauf si une fiche indique des réglages locaux différents.
Réglages
Réglages par défaut du Canada
Dernière vérification
-Pas encore vérifiée
+2026-08-29
Coordonnées
diff --git a/docs/provinces/quebec.md b/docs/provinces/quebec.md
index 5f737b95..c38b871b 100644
--- a/docs/provinces/quebec.md
+++ b/docs/provinces/quebec.md
@@ -8,8 +8,8 @@ task: browse-community-directory
scope: canada-baseline
status: draft
owner: directory-stewards
-last_reviewed: 2026-07-19
-review_by: 2027-01-15
+last_reviewed: 2026-08-29
+review_by: 2027-02-28
difficulty: beginner
estimated_time: 2-5 minutes
destructive: false
@@ -39,7 +39,7 @@ card lists different local settings.
Settings
Uses the Canada defaults
Last verified
-2026-07-31
+2026-08-29
Contacts
@@ -61,14 +61,14 @@ card lists different local settings.
Settings
Uses the Canada defaults
Last verified
-Not yet verified
+2026-08-29
Contacts
@@ -82,14 +82,14 @@ card lists different local settings.
Settings
Uses the Canada defaults
Last verified
-Not yet verified
+2026-08-29
Contacts
@@ -103,14 +103,14 @@ card lists different local settings.
Settings
Uses the Canada defaults
Last verified
-Not yet verified
+2026-08-29
Contacts
@@ -124,16 +124,15 @@ card lists different local settings.
Settings
Uses the Canada defaults
Last verified
-Not yet verified
+2026-08-29
Contacts
@@ -147,14 +146,14 @@ card lists different local settings.
Settings
Uses the Canada defaults
Last verified
-Not yet verified
+2026-08-29
Contacts
diff --git a/docs/provinces/saskatchewan.fr.md b/docs/provinces/saskatchewan.fr.md
index 2f7e0f81..2df0c32f 100644
--- a/docs/provinces/saskatchewan.fr.md
+++ b/docs/provinces/saskatchewan.fr.md
@@ -8,8 +8,8 @@ task: browse-community-directory
scope: canada-baseline
status: draft
owner: directory-stewards
-last_reviewed: 2026-07-19
-review_by: 2027-01-15
+last_reviewed: 2026-08-29
+review_by: 2027-02-28
difficulty: beginner
estimated_time: 2-5 minutes
destructive: false
@@ -39,14 +39,14 @@ sauf si une fiche indique des réglages locaux différents.
Réglages
Réglages par défaut du Canada
Dernière vérification
-Pas encore vérifiée
+2026-08-29
Coordonnées
@@ -60,7 +60,7 @@ sauf si une fiche indique des réglages locaux différents.
Réglages
Réglages par défaut du Canada
Dernière vérification
-Pas encore vérifiée
+2026-08-29
diff --git a/docs/provinces/saskatchewan.md b/docs/provinces/saskatchewan.md
index e4840c60..6bb1798e 100644
--- a/docs/provinces/saskatchewan.md
+++ b/docs/provinces/saskatchewan.md
@@ -8,8 +8,8 @@ task: browse-community-directory
scope: canada-baseline
status: draft
owner: directory-stewards
-last_reviewed: 2026-07-19
-review_by: 2027-01-15
+last_reviewed: 2026-08-29
+review_by: 2027-02-28
difficulty: beginner
estimated_time: 2-5 minutes
destructive: false
@@ -39,14 +39,14 @@ card lists different local settings.
Settings
Uses the Canada defaults
Last verified
-Not yet verified
+2026-08-29
Contacts
@@ -60,7 +60,7 @@ card lists different local settings.
Settings
Uses the Canada defaults
Last verified
-Not yet verified
+2026-08-29
diff --git a/docs/provinces/territories.fr.md b/docs/provinces/territories.fr.md
index f0a2ee23..0dc1cf8b 100644
--- a/docs/provinces/territories.fr.md
+++ b/docs/provinces/territories.fr.md
@@ -8,8 +8,8 @@ task: browse-community-directory
scope: canada-baseline
status: draft
owner: directory-stewards
-last_reviewed: 2026-07-19
-review_by: 2027-01-15
+last_reviewed: 2026-08-29
+review_by: 2027-02-28
difficulty: beginner
estimated_time: 2-5 minutes
destructive: false
diff --git a/docs/provinces/territories.md b/docs/provinces/territories.md
index 30ea1b3d..77c25041 100644
--- a/docs/provinces/territories.md
+++ b/docs/provinces/territories.md
@@ -8,8 +8,8 @@ task: browse-community-directory
scope: canada-baseline
status: draft
owner: directory-stewards
-last_reviewed: 2026-07-19
-review_by: 2027-01-15
+last_reviewed: 2026-08-29
+review_by: 2027-02-28
difficulty: beginner
estimated_time: 2-5 minutes
destructive: false
diff --git a/mkdocs.yml b/mkdocs.yml
index d6535609..171f6e2b 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -13,8 +13,8 @@ theme:
name: material
custom_dir: overrides
language: en
- favicon: assets/MeshCore-Canada-Favicon.png
- logo: assets/MeshCore-Canada-Favicon.png
+ favicon: assets/MeshCore-Canada-Favicon.svg
+ logo: assets/MeshCore-Canada-Mark.svg
font: false
palette:
- scheme: slate
@@ -49,7 +49,7 @@ extra_css:
- assets/styles/components.css?v=20260728-1
extra_javascript:
- - assets/javascripts/i18n-runtime.js?v=20260723-1
+ - assets/javascripts/i18n-runtime.js?v=20260829-1
- assets/javascripts/bootstrap.js?v=20260821-1
extra:
diff --git a/schemas/community-directory.schema.json b/schemas/community-directory.schema.json
index 7fbe6953..d22e5696 100644
--- a/schemas/community-directory.schema.json
+++ b/schemas/community-directory.schema.json
@@ -25,6 +25,7 @@
"required": [
"owner",
"migrated_at",
+ "last_reviewed",
"source_revision",
"review_by",
"update_route"
@@ -38,6 +39,10 @@
"type": "string",
"format": "date"
},
+ "last_reviewed": {
+ "type": "string",
+ "format": "date"
+ },
"source_revision": {
"type": "string",
"minLength": 7
diff --git a/scripts/validate-communities.py b/scripts/validate-communities.py
index 8edb09e1..95814a68 100644
--- a/scripts/validate-communities.py
+++ b/scripts/validate-communities.py
@@ -132,10 +132,12 @@ def validate_contact(contact: Any, label: str, check: Validation, *, with_identi
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 checked is None:
+ check.error(f"{label} must record when the contact was last checked")
+ elif checked > date.today():
+ check.error(f"{label}.last_checked cannot be in the future")
+ if contact["health"] == "expired":
+ check.error(f"{label} is expired; replace or remove it before publishing")
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")
@@ -179,7 +181,12 @@ def validate_data(data: dict[str, Any]) -> Validation:
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)
+ last_reviewed = parse_date(metadata.get("last_reviewed"), "metadata.last_reviewed", check)
+ if last_reviewed is not None and last_reviewed > date.today():
+ check.error("metadata.last_reviewed cannot be in the future")
+ review_by = parse_date(metadata.get("review_by"), "metadata.review_by", check)
+ if review_by is not None and review_by < date.today():
+ check.error("metadata.review_by has passed; audit the directory before publishing")
defaults = data.get("national_defaults")
if not isinstance(defaults, dict):
@@ -339,10 +346,14 @@ def validate_data(data: dict[str, Any]) -> Validation:
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 verified > date.today():
+ check.error(f"{label}.verified_at cannot be in the future")
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")
+ check.error(f"{community.get('name', community_id)} needs a community verification date")
+ elif verify_by is not None and verify_by < date.today():
+ check.error(f"{community.get('name', community_id)} is overdue for verification")
expected_route = ""
if province in code_pages:
@@ -486,7 +497,7 @@ def front_matter(*, title: str, description: str, task: str, metadata: dict[str,
"scope: canada-baseline",
"status: draft",
f"owner: {metadata['owner']}",
- f"last_reviewed: {metadata['migrated_at']}",
+ f"last_reviewed: {metadata['last_reviewed']}",
f"review_by: {metadata['review_by']}",
"difficulty: beginner",
"estimated_time: 2-5 minutes",
diff --git a/tests/browser/critical-journeys.spec.mjs b/tests/browser/critical-journeys.spec.mjs
index 3d6e8276..800d78c0 100644
--- a/tests/browser/critical-journeys.spec.mjs
+++ b/tests/browser/critical-journeys.spec.mjs
@@ -396,6 +396,26 @@ test("boundary editor makes both proposal paths and review-only behavior explici
await expect(page.getByText(/does not change the map/i)).toBeVisible();
});
+test("boundary editor waits to load editing data and anti-spam", async ({ page }, testInfo) => {
+ test.skip(testInfo.project.name !== "chromium", "One browser proves the shared startup contract");
+ const requests = [];
+ page.on("request", (request) => requests.push(request.url()));
+
+ await page.goto(siteRoute("/config/editor/"), { waitUntil: "networkidle" });
+
+ await expect(page.locator("html")).toHaveAttribute("data-editor-state", "waiting");
+ expect(requests.some((url) => url.includes("canada-region-membership.csv"))).toBeFalsy();
+ expect(requests.some((url) => url.includes("canada-region-partition.geojson"))).toBeFalsy();
+ expect(requests.some((url) => url.startsWith("https://challenges.cloudflare.com/"))).toBeFalsy();
+
+ await page.getByLabel("Adjust an existing boundary").check();
+ await expect(page.locator("html")).toHaveAttribute("data-editor-state", "ready");
+ expect(requests.some((url) => url.includes("canada-region-membership.csv"))).toBeTruthy();
+ expect(requests.some((url) => url.includes("cells-35.topo.json"))).toBeTruthy();
+ expect(requests.some((url) => url.includes("canada-region-partition.geojson"))).toBeFalsy();
+ expect(requests.some((url) => url.startsWith("https://challenges.cloudflare.com/"))).toBeFalsy();
+});
+
test("idea form exposes review, verification, and final submission", async ({ page }) => {
await page.goto(siteRoute("/submit-idea/"));
await expect(page.getByRole("button", { name: "Review idea" })).toBeVisible();
diff --git a/tests/content/community-directory.test.mjs b/tests/content/community-directory.test.mjs
index bfbbabbb..a3129e36 100644
--- a/tests/content/community-directory.test.mjs
+++ b/tests/content/community-directory.test.mjs
@@ -41,7 +41,6 @@ const expectedCommunities = new Map([
[
"calgary-meshcore-network",
[
- "https://meshcorecalgary.ca/",
"https://albertamesh.ca/calgary/",
"https://discord.gg/CznDhsRWnJ",
"https://yyc.meshmapper.net/?lat=51.01674&lon=-114.00149&zoom=11.00",
@@ -85,7 +84,7 @@ const expectedCommunities = new Map([
"yyc-meshcore-discord",
["https://discord.gg/CznDhsRWnJ", "https://meshmonitoring.com/"],
],
- ["stoonmesh", ["https://discord.gg/7yGnJuMGkG"]],
+ ["stoonmesh", ["https://t.me/MeshtSaska"]],
["yqrmesh", []],
[
"greater-ottawa-mesh-enthusiasts",
@@ -97,13 +96,12 @@ const expectedCommunities = new Map([
["https://discord.gg/V5esJEP67X", "https://quintemesh.ca/"],
],
["charlevoix-yml", ["https://chxmesh.ca/"]],
- ["mesh-quebec", ["https://qcmesh.net"]],
+ ["mesh-quebec", ["https://t.me/meshtQuebec"]],
["montreal-mesh", ["https://www.montrealmesh.ca"]],
["reseau-mesh-capitale-yqb", ["https://discord.gg/UhGjTF2MfA"]],
[
"reseau-mesh-saguenay-lac-saint-jean-ytf",
[
- "https://discord.gg/wUR394yXt",
"https://www.facebook.com/share/g/1GjkHAyZAM/",
"https://ytf.meshmapper.net/",
],
@@ -150,7 +148,7 @@ function idsMatching(query) {
.map((community) => community.id);
}
-test("all 24 structured listings and every curated contact URL are preserved", () => {
+test("all 24 structured listings match the reviewed contact fixture", () => {
assert.equal(data.communities.length, 24);
assert.deepEqual(
new Set(data.communities.map((community) => community.id)),
@@ -172,8 +170,8 @@ test("all 24 structured listings and every curated contact URL are preserved", (
type: "telegram",
label: "Alberta topic in MeshCore Canada",
url: "https://t.me/MeshCoreCAN",
- health: "needs-review",
- last_checked: null,
+ health: "verified",
+ last_checked: "2026-08-29",
},
]);
});
@@ -269,8 +267,11 @@ test("overview counts, empty states, and page metadata are generated", () => {
assert.match(markdown, /\ntitle: .+\n/);
assert.match(markdown, /\ndescription: .+\n/);
assert.match(markdown, /\nowner: directory-stewards\n/);
- assert.match(markdown, /\nlast_reviewed: 2026-07-19\n/);
- assert.match(markdown, /\nreview_by: 2027-01-15\n/);
+ assert.match(
+ markdown,
+ new RegExp(`\\nlast_reviewed: ${data.metadata.last_reviewed}\\n`),
+ );
+ assert.match(markdown, new RegExp(`\\nreview_by: ${data.metadata.review_by}\\n`));
assert.match(markdown, /\npage_styles:\n - assets\/styles\/communities\.css(?:\?v=\d{8}-\d+)?\n/);
const communities = data.communities.filter((community) =>
@@ -314,12 +315,12 @@ test("all listings inherit the three-byte Canada baseline", () => {
const quebec = readFileSync(join(provinceDir, "quebec.md"), "utf8");
assert.match(
quebec,
- /Réseau MESH de Charlevoix \(YML\)<\/h3>[\s\S]*?Listing contact:<\/strong> pifane[\s\S]*?Contact check:<\/strong> Verified on 2026-08-08/,
+ /Réseau MESH de Charlevoix \(YML\)<\/h3>[\s\S]*?Listing contact:<\/strong> pifane[\s\S]*?Contact check:<\/strong> Verified on 2026-08-29/,
);
const quebecFr = readFileSync(join(provinceDir, "quebec.fr.md"), "utf8");
assert.match(
quebecFr,
- /Réseau MESH de Charlevoix \(YML\)<\/h3>[\s\S]*?Contact pour cette fiche :<\/strong> pifane[\s\S]*?Vérification :<\/strong> Effectuée le 2026-08-08/,
+ /Réseau MESH de Charlevoix \(YML\)<\/h3>[\s\S]*?Contact pour cette fiche :<\/strong> pifane[\s\S]*?Vérification :<\/strong> Effectuée le 2026-08-29/,
);
const saskatchewan = readFileSync(
diff --git a/tests/content/p0-content-safety.test.mjs b/tests/content/p0-content-safety.test.mjs
index efa659c6..e0827d49 100644
--- a/tests/content/p0-content-safety.test.mjs
+++ b/tests/content/p0-content-safety.test.mjs
@@ -1,5 +1,5 @@
import assert from "node:assert/strict";
-import { existsSync, readFileSync } from "node:fs";
+import { existsSync, readFileSync, statSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import test from "node:test";
@@ -66,6 +66,21 @@ test("unsafe and incomplete legacy pages are removed from the content tree", ()
}
});
+test("header and favicon use small, separate vector assets", () => {
+ const config = read("mkdocs.yml");
+ const assets = [
+ "docs/assets/MeshCore-Canada-Mark.svg",
+ "docs/assets/MeshCore-Canada-Favicon.svg",
+ ];
+
+ assert.match(config, /logo: assets\/MeshCore-Canada-Mark\.svg/);
+ assert.match(config, /favicon: assets\/MeshCore-Canada-Favicon\.svg/);
+ assert.doesNotMatch(config, /MeshCore-Canada-Favicon\.png/);
+ for (const asset of assets) {
+ assert.ok(statSync(resolve(root, asset)).size < 3_000, `${asset} should stay lightweight`);
+ }
+});
+
test("mounting Markdown remains repaired", () => {
const mounting = read("docs/hardware/repeater-mounting-options.md");
diff --git a/tests/content/test_validate_communities.py b/tests/content/test_validate_communities.py
index ef961a6e..1f0b9340 100644
--- a/tests/content/test_validate_communities.py
+++ b/tests/content/test_validate_communities.py
@@ -1,5 +1,6 @@
from __future__ import annotations
+import copy
import importlib.util
import unittest
from pathlib import Path
@@ -33,5 +34,34 @@ def test_expired_contact_takes_priority(self) -> None:
self.assertEqual("Un lien doit être mis à jour", MODULE.contact_check_label_fr(contacts))
+class ReviewDeadlineTests(unittest.TestCase):
+ def test_overdue_directory_review_fails_validation(self) -> None:
+ data = copy.deepcopy(MODULE.load_data())
+ data["metadata"]["review_by"] = "2000-01-01"
+
+ errors = MODULE.validate_data(data).errors
+
+ self.assertTrue(any("metadata.review_by has passed" in error for error in errors))
+
+ def test_expired_contacts_and_overdue_listings_fail_validation(self) -> None:
+ data = copy.deepcopy(MODULE.load_data())
+ community = data["communities"][0]
+ community["verify_by"] = "2000-01-01"
+ community["contacts"][0]["health"] = "expired"
+
+ errors = MODULE.validate_data(data).errors
+
+ self.assertTrue(any("is expired" in error for error in errors))
+ self.assertTrue(any("overdue for verification" in error for error in errors))
+
+ def test_every_kept_contact_requires_a_check_date(self) -> None:
+ data = copy.deepcopy(MODULE.load_data())
+ data["communities"][0]["contacts"][0]["last_checked"] = None
+
+ errors = MODULE.validate_data(data).errors
+
+ self.assertTrue(any("must record when the contact was last checked" in error for error in errors))
+
+
if __name__ == "__main__":
unittest.main()
diff --git a/tests/editor/editor-containment.test.mjs b/tests/editor/editor-containment.test.mjs
index bac41cf3..453dfc8b 100644
--- a/tests/editor/editor-containment.test.mjs
+++ b/tests/editor/editor-containment.test.mjs
@@ -19,6 +19,17 @@ test("proposal type is explicit before geography and new-region eligibility is v
assert.match(html, /does not change\s+the map/);
});
+test("heavy editor data and anti-spam load only after the visitor starts", async () => {
+ const [html, app] = await Promise.all([source("index.html"), source("app.js")]);
+ assert.match(html, /data-editor-state="waiting"/);
+ assert.match(html, /id="review-panel"/);
+ assert.match(app, /if \(!await initialise\(\)\)/);
+ assert.match(app, /IntersectionObserver/);
+ assert.doesNotMatch(app, /canada-region-partition\.geojson/);
+ assert.doesNotMatch(app, /\n initialise\(\);\n/);
+ assert.doesNotMatch(app, /\n initialiseSubmission\(\);\n/);
+});
+
test("editor exposes a complete structured alternative to the optional map", async () => {
const html = await source("index.html");
assert.match(html, /id="municipality-select"/);