diff --git a/data/communities.fr.json b/data/communities.fr.json index daca101..f71c39c 100644 --- a/data/communities.fr.json +++ b/data/communities.fr.json @@ -93,6 +93,9 @@ "yqrmesh": { "service_area": "Regina et sud de la Saskatchewan" }, + "winnipeg-meshcore": { + "service_area": "Winnipeg (Manitoba)" + }, "greater-ottawa-mesh-enthusiasts": { "service_area": "Est de l’Ontario et ouest du Québec" }, diff --git a/data/communities.json b/data/communities.json index 628f285..f53346d 100644 --- a/data/communities.json +++ b/data/communities.json @@ -821,16 +821,16 @@ }, "contacts": [ { - "type": "telegram", - "label": "Mesh Saskatchewan Telegram", - "url": "https://t.me/MeshtSaska", + "type": "discord", + "label": "StoonMesh Discord", + "url": "https://discord.gg/StFvPY7BZe", "health": "verified", - "last_checked": "2026-08-29" + "last_checked": "2026-09-24" } ], "owner": null, - "verified_at": "2026-08-29", - "verify_by": "2027-02-28", + "verified_at": "2026-09-24", + "verify_by": "2027-03-24", "canonical_route": "/provinces/saskatchewan/#community-stoonmesh" }, { @@ -862,6 +862,50 @@ "verify_by": "2027-02-28", "canonical_route": "/provinces/saskatchewan/#community-yqrmesh" }, + { + "id": "winnipeg-meshcore", + "name": "Winnipeg MeshCore", + "province": "MB", + "service_area": "Winnipeg, Manitoba", + "places": [ + "Winnipeg" + ], + "aliases": [ + "WPG", + "YWG" + ], + "status": "forming", + "languages": [], + "location": { + "latitude": null, + "longitude": null, + "precision": "service-area" + }, + "settings": { + "inherit_national": true, + "overrides": {} + }, + "contacts": [ + { + "type": "website", + "label": "Winnipeg MeshCore website", + "url": "https://winnipegmeshcore.neocities.org/", + "health": "verified", + "last_checked": "2026-09-24" + }, + { + "type": "matrix", + "label": "Winnipeg MeshCore chat", + "url": "https://matrix.to/#/#winnipeg-meshcore:matrix.org", + "health": "verified", + "last_checked": "2026-09-24" + } + ], + "owner": null, + "verified_at": "2026-09-24", + "verify_by": "2027-03-24", + "canonical_route": "/provinces/manitoba/#community-winnipeg-meshcore" + }, { "id": "greater-ottawa-mesh-enthusiasts", "name": "Greater Ottawa Mesh Enthusiasts", diff --git a/data/community-search-anchors.json b/data/community-search-anchors.json index 0bc6c42..a5acc3c 100644 --- a/data/community-search-anchors.json +++ b/data/community-search-anchors.json @@ -14,6 +14,7 @@ "yyc-meshcore-discord": ["cal"], "stoonmesh": ["stoon"], "yqrmesh": ["regina"], + "winnipeg-meshcore": ["wpg"], "greater-ottawa-mesh-enthusiasts": ["ott", "gatout"], "gta-lora-meshes": ["tor"], "quinte-mesh-network": ["has", "pec"], diff --git a/docs/provinces/index.fr.md b/docs/provinces/index.fr.md index 1fea6ff..8212cc2 100644 --- a/docs/provinces/index.fr.md +++ b/docs/provinces/index.fr.md @@ -31,9 +31,9 @@ Filtrez la liste par communauté ou province, ou cherchez votre ville pour trouv Ces renseignements pourraient être périmés !
-24 fiches +25 fiches 23 fiches actives -1 fiche en formation +2 fiches en formation 1 fiche avec des réglages locaux différents
@@ -57,7 +57,7 @@ Filtrez la liste par communauté ou province, ou cherchez votre ville pour trouv
- 24 communautés affichées + 25 communautés affichées
@@ -274,9 +274,9 @@ Filtrez la liste par communauté ou province, ou cherchez votre ville pour trouv

Centre de la Saskatchewan

Province ou territoire : Saskatchewan

Réglages : Réglages par défaut du Canada

-

Fiche révisée : 2026-08-29

+

Fiche révisée : 2026-09-24

Voir les détails de la fiche

@@ -293,6 +293,21 @@ Filtrez la liste par communauté ou province, ou cherchez votre ville pour trouv

Voir les détails de la fiche

+
+
+

Winnipeg MeshCore

+En formation +
+

Winnipeg (Manitoba)

+

Province ou territoire : Manitoba

+

Réglages : Réglages par défaut du Canada

+

Fiche révisée : 2026-09-24

+ +

Voir les détails de la fiche

+

Greater Ottawa Mesh Enthusiasts

@@ -485,7 +500,7 @@ Utilisez ces réglages sauf si votre communauté locale en indique d’autres.

Manitoba

-

Aucune fiche

+

1 en formation

Ontario

diff --git a/docs/provinces/index.md b/docs/provinces/index.md index 3fdb30b..9b9fba7 100644 --- a/docs/provinces/index.md +++ b/docs/provinces/index.md @@ -31,9 +31,9 @@ Type a community or province to filter the list, or look up your city to find th This information could be out of date!
-24 listings +25 listings 23 listed active -1 listed forming +2 listed forming 1 with different local settings
@@ -57,7 +57,7 @@ Type a community or province to filter the list, or look up your city to find th
- Showing 24 communities + Showing 25 communities
@@ -286,9 +286,9 @@ Type a community or province to filter the list, or look up your city to find th

Province: Saskatchewan

Settings: Uses the Canada defaults

-

Listing reviewed: 2026-08-29

+

Listing reviewed: 2026-09-24

View listing details

@@ -306,6 +306,22 @@ Type a community or province to filter the list, or look up your city to find th

View listing details

+
+
+

Winnipeg MeshCore

+Forming +
+

Winnipeg, Manitoba

+ +

Province: Manitoba

+

Settings: Uses the Canada defaults

+

Listing reviewed: 2026-09-24

+ +

View listing details

+

Greater Ottawa Mesh Enthusiasts

@@ -509,7 +525,7 @@ Use these settings unless your local community lists different ones.

Manitoba

-

No listing yet

+

1 forming

Ontario

diff --git a/docs/provinces/manitoba.fr.md b/docs/provinces/manitoba.fr.md index f061999..45e2e57 100644 --- a/docs/provinces/manitoba.fr.md +++ b/docs/provinces/manitoba.fr.md @@ -21,18 +21,40 @@ page_styles: # Communautés MeshCore au Manitoba -Aucune communauté n’est encore répertoriée au Manitoba. +Il y a **1 fiche de communauté en formation** au Manitoba. -
-

Aidez-nous à ajouter la première fiche

-

Indiquez le nom de la communauté, la zone desservie, son état et un lien de contact public.

-

Ajouter une communauté

-

Parcourir toutes les communautés canadiennes

-
+Toutes les fiches utilisent les [réglages par défaut du Canada](index.md#canada-baseline), +sauf si une fiche indique des réglages locaux différents. + +## Fiches des communautés -Tant qu’aucune fiche locale examinée n’indique d’autres réglages, commencez avec -les [réglages par défaut du Canada](index.md#canada-baseline) et confirmez-les -auprès des personnes à proximité avant de transmettre. +
+
+
+

Winnipeg MeshCore

+En formation +
+

Winnipeg (Manitoba)

+
+
Réglages
+
Réglages par défaut du Canada
+
Fiche révisée
+
2026-09-24
+
+

+Ce groupe est en formation. Communiquez avec lui pour savoir ce qui fonctionne et où votre aide serait utile. +

+

Coordonnées

+ +

+Vérification : Effectuée le 2026-09-24 +

+

Mettre cette fiche à jour

+
+
## Ajouter ou mettre à jour une fiche diff --git a/docs/provinces/manitoba.md b/docs/provinces/manitoba.md index 5fabda4..341d0d9 100644 --- a/docs/provinces/manitoba.md +++ b/docs/provinces/manitoba.md @@ -21,18 +21,40 @@ page_styles: # MeshCore communities in Manitoba -We don't have a community listed here yet. +Manitoba has **1 forming community listing**. -
-

Help add the first listing

-

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

-

Add a community

-

Browse all Canadian communities

-
+All listings use the [Canada defaults](index.md#canada-baseline) unless a +card lists different local settings. + +## Community listings -Until a reviewed local listing gives different settings, start with the -[Canada defaults](index.md#canada-baseline) and confirm settings with nearby -operators before transmitting. +
+
+
+

Winnipeg MeshCore

+Forming +
+

Winnipeg, Manitoba

+
+
Settings
+
Uses the Canada defaults
+
Listing reviewed
+
2026-09-24
+
+

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

+

Contacts

+ +

+Contact check: Links checked on 2026-09-24 +

+

Update this listing

+
+
## Add or update a listing diff --git a/docs/provinces/saskatchewan.fr.md b/docs/provinces/saskatchewan.fr.md index 3ad1950..23ccd4d 100644 --- a/docs/provinces/saskatchewan.fr.md +++ b/docs/provinces/saskatchewan.fr.md @@ -39,14 +39,14 @@ sauf si une fiche indique des réglages locaux différents.
Réglages
Réglages par défaut du Canada
Fiche révisée
-
2026-08-29
+
2026-09-24

Coordonnées

-Vérification : Effectuée le 2026-08-29 +Vérification : Effectuée le 2026-09-24

Mettre cette fiche à jour

diff --git a/docs/provinces/saskatchewan.md b/docs/provinces/saskatchewan.md index 2f6ffc8..f7d494b 100644 --- a/docs/provinces/saskatchewan.md +++ b/docs/provinces/saskatchewan.md @@ -39,14 +39,14 @@ card lists different local settings.
Settings
Uses the Canada defaults
Listing reviewed
-
2026-08-29
+
2026-09-24

Contacts

-Contact check: Links checked on 2026-08-29 +Contact check: Links checked on 2026-09-24

Update this listing

diff --git a/schemas/community-directory.schema.json b/schemas/community-directory.schema.json index d22e569..d6bb4cf 100644 --- a/schemas/community-directory.schema.json +++ b/schemas/community-directory.schema.json @@ -186,6 +186,7 @@ "discord", "facebook", "instagram", + "matrix", "meshmapper", "reddit", "telegram", diff --git a/scripts/validate-communities.py b/scripts/validate-communities.py index 1d35c18..ce7fe4b 100644 --- a/scripts/validate-communities.py +++ b/scripts/validate-communities.py @@ -46,6 +46,7 @@ "discord", "facebook", "instagram", + "matrix", "meshmapper", "reddit", "telegram", @@ -1083,6 +1084,7 @@ def contact_type_label_fr(contact_type: str) -> str: "discord": "Discord", "facebook": "Facebook", "instagram": "Instagram", + "matrix": "Matrix", "meshmapper": "MeshMapper", "reddit": "Reddit", "telegram": "Telegram", @@ -1126,6 +1128,8 @@ def contact_label_fr(label: str) -> str: "Cardston topic in Mesh Alberta": "Sujet sur Cardston dans Mesh Alberta", "YYC MeshCore Discord": "Discord de YYC MeshCore", "StoonMesh Discord": "Discord de StoonMesh", + "Winnipeg MeshCore website": "Site Web de Winnipeg MeshCore", + "Winnipeg MeshCore chat": "Salon de Winnipeg MeshCore", "Greater Ottawa Mesh Enthusiasts Discord": "Discord de Greater Ottawa Mesh Enthusiasts", "Ottawa Mesh website": "Site Web d’Ottawa Mesh", "GTA+-Lora-Meshes Discord": "Discord de GTA+-Lora-Meshes", diff --git a/tests/browser/critical-journeys.spec.mjs b/tests/browser/critical-journeys.spec.mjs index 132a86d..ba6c377 100644 --- a/tests/browser/critical-journeys.spec.mjs +++ b/tests/browser/critical-journeys.spec.mjs @@ -293,6 +293,29 @@ test("community directory deep links use their dedicated query parameter", async expect(url.searchParams.has("q")).toBeFalsy(); }); +for (const locale of ["", "fr/"]) { + test(`${locale || "en/"} community updates expose StoonMesh and Winnipeg contacts`, async ({ page }) => { + await page.goto(siteRoute(`/${locale}provinces/?community=StoonMesh`)); + const stoon = page.locator("#directory-stoonmesh"); + await expect(stoon).toBeVisible(); + await expect(stoon.locator('a[href="https://discord.gg/StFvPY7BZe"]')).toBeVisible(); + await expect(stoon.locator('a[href*="t.me"]')).toHaveCount(0); + await stoon.locator("h3 a").click(); + await expect(page.locator('#community-stoonmesh a[href="https://discord.gg/StFvPY7BZe"]')).toBeVisible(); + + await page.goto(siteRoute(`/${locale}provinces/?community=Winnipeg`)); + const winnipeg = page.locator("#directory-winnipeg-meshcore"); + await expect(page.locator("[data-community-card]:visible")).toHaveCount(1); + await expect(winnipeg.locator('[data-status="forming"]')).toHaveText(locale ? "En formation" : "Forming"); + await expect(winnipeg.locator('a[href="https://matrix.to/#/#winnipeg-meshcore:matrix.org"]')).toBeVisible(); + await winnipeg.locator("h3 a").click(); + await expect(page).toHaveURL(new RegExp(`/${locale}provinces/manitoba/#community-winnipeg-meshcore$`)); + const detail = page.locator("#community-winnipeg-meshcore"); + await expect(detail.locator('a[href="https://winnipegmeshcore.neocities.org/"]')).toBeVisible(); + await expect(detail.locator('a[href="https://matrix.to/#/#winnipeg-meshcore:matrix.org"]')).toBeVisible(); + }); +} + test("BC Mesh's local frequency is searchable in both directories", async ({ page }) => { for (const [route, count] of [ ["/provinces/", "Showing 1 community"], diff --git a/tests/browser/site-feedback.spec.mjs b/tests/browser/site-feedback.spec.mjs index 2a48495..55f377a 100644 --- a/tests/browser/site-feedback.spec.mjs +++ b/tests/browser/site-feedback.spec.mjs @@ -1,7 +1,11 @@ import { expect, test } from "./site-fixtures.mjs"; import AxeBuilder from "@axe-core/playwright"; +import { readFileSync } from "node:fs"; import { siteRoute } from "./site-route.mjs"; +const communityCount = JSON.parse( + readFileSync(new URL("../../data/communities.json", import.meta.url), "utf8"), +).communities.length; const overview = { activeObservers: 12, activeIatas: 4, totalPackets: 900, windowHours: 24 }; const types = [{ nodeType: 2, count: 50 }, { nodeType: 1, count: 20 }, { nodeType: 3, count: 3 }, { nodeType: 4, count: 2 }]; @@ -39,10 +43,10 @@ for (const locale of ["", "fr/"]) { expect(query.searchParams.get("keys")).toBe("geonames"); expect(query.searchParams.get("lang")).toBe(locale ? "fr" : "en"); await page.locator("[data-community-show-all]").click(); - await expect(cards).toHaveCount(24); + await expect(cards).toHaveCount(communityCount); await page.locator("[data-community-clear]").first().click(); await expect(page.locator("#community-search")).toHaveValue(""); - await expect(cards).toHaveCount(24); + await expect(cards).toHaveCount(communityCount); await expect(cards.first()).toHaveAttribute("id", "directory-bc-mesh"); expect(lookups).toHaveLength(1); }); diff --git a/tests/content/browser-feedback.test.mjs b/tests/content/browser-feedback.test.mjs index 24ab4bd..66838b2 100644 --- a/tests/content/browser-feedback.test.mjs +++ b/tests/content/browser-feedback.test.mjs @@ -56,7 +56,7 @@ test("Beacon totals distinguish known devices from the overview time window", () assert.throws(() => network.parseStats({ ...overview, windowHours: 0 }, types)); }); -test("all 24 listings have finite geographic search references without changing their source locations", () => { +test("all listings have finite geographic search references without changing their source locations", () => { const directory = JSON.parse(readFileSync("data/communities.json", "utf8")); const anchors = JSON.parse(readFileSync("data/community-search-anchors.json", "utf8")); assert.deepEqual(Object.keys(anchors.communities).sort(), directory.communities.map(item => item.id).sort()); @@ -64,7 +64,7 @@ test("all 24 listings have finite geographic search references without changing const page = readFileSync(`docs/provinces/${locale}`, "utf8"); assert.match(page, /status_notice: false/); const points = [...page.matchAll(/data-community-points="([^"]+)"/g)]; - assert.equal(points.length, 24); + assert.equal(points.length, directory.communities.length); for (const [, encoded] of points) { const list = JSON.parse(encoded.replaceAll(""", '"').replaceAll("'", "'").replaceAll("&", "&")); assert.ok(list.length > 0); diff --git a/tests/content/community-directory.test.mjs b/tests/content/community-directory.test.mjs index 620e9b9..3750a52 100644 --- a/tests/content/community-directory.test.mjs +++ b/tests/content/community-directory.test.mjs @@ -84,8 +84,12 @@ const expectedCommunities = new Map([ "yyc-meshcore-discord", ["https://discord.gg/CznDhsRWnJ", "https://meshmonitoring.com/"], ], - ["stoonmesh", ["https://t.me/MeshtSaska"]], + ["stoonmesh", ["https://discord.gg/StFvPY7BZe"]], ["yqrmesh", []], + [ + "winnipeg-meshcore", + ["https://winnipegmeshcore.neocities.org/", "https://matrix.to/#/#winnipeg-meshcore:matrix.org"], + ], [ "greater-ottawa-mesh-enthusiasts", ["https://discord.gg/WSyNd8SfNr", "https://ottawamesh.ca/"], @@ -148,8 +152,8 @@ function idsMatching(query) { .map((community) => community.id); } -test("all 24 structured listings match the reviewed contact fixture", () => { - assert.equal(data.communities.length, 24); +test("all structured listings match the reviewed contact fixture", () => { + assert.equal(data.communities.length, expectedCommunities.size); assert.deepEqual( new Set(data.communities.map((community) => community.id)), new Set(expectedCommunities.keys()), @@ -185,7 +189,7 @@ test("the generated directory cannot drift from structured data", () => { encoding: "utf8", }, ); - assert.match(output, /Community directory validated: 24 listings/); + assert.match(output, new RegExp(`Community directory validated: ${expectedCommunities.size} listings`)); }); test("search covers reviewed place names and common aliases", () => { @@ -205,6 +209,9 @@ test("search covers reviewed place names and common aliases", () => { ]); assert.deepEqual(idsMatching("YQR"), ["yqrmesh"]); assert.deepEqual(idsMatching("Regina"), ["yqrmesh"]); + assert.deepEqual(idsMatching("Winnipeg"), ["winnipeg-meshcore"]); + assert.deepEqual(idsMatching("WPG"), ["winnipeg-meshcore"]); + assert.deepEqual(idsMatching("YWG"), ["winnipeg-meshcore"]); assert.deepEqual(idsMatching("Montréal"), ["montreal-mesh", "reseau-libre"]); assert.deepEqual(idsMatching("Airdrie"), [ "alberta-meshcore-networks", @@ -377,6 +384,36 @@ test("BC Mesh documents the issue 79 frequency override in both languages", () = } }); +test("issues 94 and 95 keep corrected contacts and Winnipeg status in both languages", () => { + const winnipeg = data.communities.find(community => community.id === "winnipeg-meshcore"); + assert.equal(winnipeg.province, "MB"); + assert.equal(winnipeg.status, "forming"); + assert.deepEqual(winnipeg.settings, { inherit_national: true, overrides: {} }); + assert.deepEqual(winnipeg.contacts.map(contact => contact.type), ["website", "matrix"]); + const schema = JSON.parse(readFileSync(join(root, "schemas/community-directory.schema.json"), "utf8")); + assert.ok(schema.$defs.contact.properties.type.enum.includes("matrix")); + const anchors = JSON.parse(readFileSync(join(root, "data/community-search-anchors.json"), "utf8")); + assert.deepEqual(anchors.communities[winnipeg.id], ["wpg"]); + + for (const suffix of [".md", ".fr.md"]) { + const index = readFileSync(join(provinceDir, `index${suffix}`), "utf8"); + const saskatchewan = readFileSync(join(provinceDir, `saskatchewan${suffix}`), "utf8"); + const manitoba = readFileSync(join(provinceDir, `manitoba${suffix}`), "utf8"); + for (const page of [index, saskatchewan]) { + assert.ok(page.includes('href="https://discord.gg/StFvPY7BZe"')); + assert.ok(!page.includes("MeshtSaska")); + assert.ok(!page.includes("Mesh Saskatchewan Telegram")); + } + for (const page of [index, manitoba]) { + assert.ok(page.includes('href="https://winnipegmeshcore.neocities.org/"')); + assert.match(page, /Matrix\s*:<\/strong>/); + assert.ok(page.includes('href="https://matrix.to/#/#winnipeg-meshcore:matrix.org"')); + } + assert.ok(manitoba.includes('id="community-winnipeg-meshcore"')); + assert.match(manitoba, /data-status="forming">(?:Forming|En formation) { const yqlmesh = data.communities.find( (community) => community.id === "yqlmesh",