Index: src/routes/guardian.js
===================================================================
--- src/routes/guardian.js	(revision 792af539e3b665ec03cb8927cd16b3af858defec)
+++ src/routes/guardian.js	(revision 7cfe35a765e2a976eee4375edecec2515ec7677c)
@@ -63,5 +63,8 @@
     'gate_kind_setting', 'gate_kind_perRequest', 'gate_kind_handover',
     'gate_unknown', 'gate_threshold', 'gate_threshold_unknown',
-    'gate_irreversible', 'gate_waiting', 'gate_blocked', 'gate_propose'];
+    'gate_irreversible', 'gate_waiting', 'gate_blocked', 'gate_propose',
+    // Oppikken en afhandelen van een hulpvraag (shaer-lgo).
+    'help_pick', 'help_close', 'help_picked_by', 'help_handled_by', 'help_handled_note',
+    'help_close_ask', 'help_close_yes', 'help_just_now', 'help_hours', 'help_days'];
   const s = Object.fromEntries(keys.map((k) => [k, i18nT(L, `guardian.${k}`)]));
   s.wave = i18nT(L, 'guardian.wave');
@@ -77,6 +80,14 @@
             emoji_json, actor_emoji_json, media_json, quote_json, embed_json
      FROM ap_mentions WHERE slug = ? AND help_request = 1 ORDER BY created_at DESC LIMIT 50`
-  ).all(site.slug).map((h) => ({
+  ).all(site.slug);
+  // De gedeelde staat in EEN query (shaer-lgo): wie er al op af is en of het is
+  // afgesloten. Per kaart vragen zou hier een N+1 opleveren, en dit is precies
+  // het scherm dat een guardian in een haast openslaat.
+  const helpStaat = Guardianship.help.statusFor(help.map((h) => h.object_uri));
+  const helpItems = help.map((h) => ({
     ...h,
+    // Bij twijfel OPEN. Een hulpvraag die er afgehandeld uitziet terwijl hij dat
+    // niet is, is de gevaarlijke fout -- niet andersom.
+    state: helpStaat.get(h.object_uri) || { open: true, pickedUpBy: [], handled: null, ageMs: null },
     // The dashboard is built in the browser, so it gets the body finished: the
     // same partial de Krant and Berichten use. A 🛟 often carries a screenshot
@@ -123,5 +134,5 @@
       id: r.id, ward: r.ward_uri, proposer: r.proposer, feature: r.feature, value: !!r.value,
     })),
-    help,
+    help: helpItems,
     strings: uiStrings(L),
   };
@@ -336,4 +347,42 @@
   if (!r) return res.status(502).json({ error: 'delivery' });
   res.json({ ok: true, delivered: r.delivered });
+});
+
+// ── Een hulpvraag oppikken of afsluiten (shaer-lgo) ───────────────
+// Gaat naar de WARD en naar de MEDE-GUARDIANS. De ward hoort te weten dat er
+// iemand komt -- dat is de helft van de gerustheid -- en de anderen dat het
+// loopt, zodat niemand denkt dat de ander het al doet.
+//
+// OPPIKKEN mag stapelen: twee mensen die tegelijk reageren is geen probleem.
+// AFSLUITEN kent geen terugdraai; leeft de vraag nog, dan wordt hij opnieuw
+// gesteld. De stevige bevestiging zit in de client, net als bij het loslaten van
+// een ward: nooit een window.confirm.
+router.post('/api/help/:kind', requireAuth, express.json({ limit: '2kb' }), async (req, res) => {
+  const site = siteForUser(req);
+  if (!site) return res.status(404).json({ error: 'no_site' });
+  const kind = req.params.kind === 'handled' ? 'handled' : 'pickup';
+  const noteUri = String(req.body?.note || '').trim();
+  const wardUri = String(req.body?.ward || '').trim();
+  if (!noteUri || !/^https?:\/\//i.test(noteUri)) return res.status(400).json({ error: 'no_note' });
+  // Alleen over een hulpvraag van een kind dat je echt bewaakt.
+  const isWard = Guardianship.listWards(site.slug).some((w) => w.other_uri === wardUri);
+  if (!isWard) return res.status(403).json({ error: 'not_your_ward' });
+
+  const me = AP.actorId((process.env.PUBLIC_BASE_URL || '').replace(/\/+$/, ''), site.slug);
+  // Onze eigen kopie meteen, zonder op bezorging te wachten: het scherm van
+  // degene die klikt hoort niet te liegen omdat een andere server traag is.
+  Guardianship.help.record(noteUri, me, kind, null);
+
+  const anderen = Guardianship.listGuardians(wardUri.replace(/.*\/ap\/users\//, '')) || [];
+  const ontvangers = [wardUri, ...anderen.map((g) => g.other_uri)].filter((u) => u && u !== me);
+  const r = await AP.deliverDirectNote(site, {
+    recipients: ontvangers,
+    text: kind === 'handled' ? 'Deze hulpvraag is afgehandeld.' : 'Ik kijk hiernaar.',
+    helpMark: { kind, noteUri },
+  }).catch(() => null);
+  // Bezorging kan mislukken; de eigen staat staat er dan toch. Dat melden we,
+  // want "verstuurd" zeggen terwijl het niet aankwam is hier het ergste soort
+  // stilte.
+  res.json({ ok: true, delivered: r ? r.delivered : 0, recipients: ontvangers.length });
 });
 
