From ee69e1ee3df0e24d81042bb35dc9ff8b241712e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20H=C3=A4gele?= Date: Sun, 27 Sep 2026 15:11:35 +0200 Subject: [PATCH] feat(app): send one broker to several collectors under one switch DutchMeshCore is one broker with two collectors behind it, and each takes its own stream label: collector 1 hunter, collector 2 wardriver. Until now every config.json entry was its own row with its own switch, so the site offered four presets and a hunter had to pick one per drive. A config.json broker can now list streams, each with its own url and label. It stays one entry: one row on the brokers page, one line in the Status tab, one switch. legsOf makes a connection of each stream under the id @, so each keeps its own watermark, companion token and socket; the drain, retention and connect code now walk connections, the rows and switches stay brokers. A row's status folds its connections ("1 of 2 connected" in between). The add form refuses a host the site already publishes to, since two sessions under the companion's key kick each other. The splash and About say where receptions go, from the brokers that are on. Verified: 1637 app and 762 web tests, eslint, build; every new test mutation-checked. In the browser against three local brokers: the own one got packets, collector 1 obs under hunter, collector 2 under wardriver; with the switch off only the own one got the next reception, and on again both collectors caught up. Not tested against the real collectors or with a companion signing. Closes #704 Co-Authored-By: Claude Opus 5.5 --- AGENTS.md | 17 +- app/changelog.json | 7 + app/index.html | 2 + app/public/config.example.json | 9 +- app/src/__tests__/brokers.test.js | 151 +++++++++++++++++- app/src/__tests__/config.test.js | 33 ++++ app/src/app.js | 58 +++++-- app/src/brokers.js | 105 +++++++++++- app/src/brokersheet.js | 13 +- app/src/config.js | 24 ++- app/src/styles/app.css | 3 + ...026-09-27-12-dutchmeshcore-by-default.json | 7 + docs/2026-09-21-mqtt-brokers.md | 21 ++- web/changelog.json | 7 + 14 files changed, 426 insertions(+), 31 deletions(-) create mode 100644 changelog.d/2026-09-27-12-dutchmeshcore-by-default.json diff --git a/AGENTS.md b/AGENTS.md index e0c77960..cec134a0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -764,6 +764,12 @@ default is `hunter`. `test` is a collector's sandbox region, not a label for liv either message. A broker marked `auth: "companion"` is signed in to as `v1_{PUBKEY}` with a token the companion signs itself (`app/src/companionsign.js`). +A `config.json` broker can list `streams` instead of one `url` (#704): each stream has its own +`url` and `label`, and the broker stays one row with one switch. Each stream is its own connection +(`legsOf` in `app/src/brokers.js`), under the id `@`, with its own watermark and +token. The site configures DutchMeshCore this way: collector 1 under `hunter`, collector 2 under +`wardriver`. + --- ## 10. Resilience invariants (do not break) @@ -775,11 +781,12 @@ the companion signs itself (`app/src/companionsign.js`). published is never deleted, however old — an offline phone keeps everything until it drains. IndexedDB is the working set; the backend deduplicates. Publication is tracked by a durable watermark, not an in-memory set, so a restart does not re-publish the store. - There is one watermark per broker (#554): each broker drains on its own, so one that is offline - does not hold the others back, and retention deletes only below the lowest watermark of the - brokers that are on. A broker switched off keeps its backlog for the retention window, not - beyond it; with every broker off, all of them count and nothing unsent is deleted - (`owedBrokers` in `app/src/brokers.js`). + There is one watermark per broker (#554), and per stream of a broker that has streams (#704): + each connection drains on its own, so one that is offline does not hold the others back, and + retention deletes only below the lowest watermark of the brokers that are on. A broker switched + off keeps its backlog for the retention window, not beyond it; with every broker off, all of them + count and nothing unsent is deleted (`owedLegs` in `app/src/brokers.js`, which asks per + connection, never per row). See `docs/2026-07-22-retention-and-bounded-reads.md` (#230); this replaces an earlier absolute "never deletes local rows" rule, which made the store unbounded. - Queue reads are **bounded** — never `getAll()` over the store. The display reads its time window via diff --git a/app/changelog.json b/app/changelog.json index b2ca7c2a..d15d527e 100644 --- a/app/changelog.json +++ b/app/changelog.json @@ -20,6 +20,13 @@ "title": "A larger audio buffer", "body": "Sound uses a larger output buffer, against ticking while the app is busy right after start." }, + { + "id": "2026-09-27-dutchmeshcore-by-default", + "date": "2026-09-27", + "where": "app", + "title": "Your receptions also go to DutchMeshCore", + "body": "Your receptions now also go to DutchMeshCore, with one switch under Manage brokers." + }, { "id": "2026-09-26-broker-presets-shown", "date": "2026-09-26", diff --git a/app/index.html b/app/index.html index e9adb8aa..ee82ec3f 100644 --- a/app/index.html +++ b/app/index.html @@ -241,6 +241,8 @@

Mesh-Hunter

floating callouts, their copy moves into the panel instead. -->

+ +