Changeset a29c8c8 in Klonkt for src/routes/guardian.js


Ignore:
Timestamp:
08/07/2026 01:51:38 PM (5 weeks ago)
Author:
roboburr <roboburr@…>
Branches:
main
Children:
7cfe35a
Parents:
792af53
git-author:
Robin <roboburr@…> (08/07/2026 01:51:36 PM)
git-committer:
roboburr <roboburr@…> (08/07/2026 01:51:38 PM)
Message:

Een hulpvraag oppikken en afsluiten, zichtbaar voor alle guardians (shaer-lgo)

Een hulpvraag (5.2.1) gaat naar ALLE guardians van een kind, op verschillende
servers. Zonder gedeelde staat denken er twee dat de ander het oppakt -- precies
het scenario waar de reddingsboei voor bestaat. Tot nu toe droeg een hulpvraag
geen enkele staat: een vlag op ap_mentions, verder niets.

DE FAALSTAND IS HIER NIET VEILIG, en dat stuurt het hele ontwerp. Bij een gate is
"dicht" het veilige antwoord. Hier is de faalstand "iedereen denkt dat het
geregeld is", en dat is gevaarlijker dan geen markering. Daarom staat een
hulpvraag bij twijfel OPEN: een lege lijst, een rij die we niet kunnen lezen, een
soort die we niet kennen -- alles wat geen expliciete afsluiting is telt als
"er wacht nog iemand".

Twee besluiten van Bart zitten in de vorm.

OPGEPIKT mag stapelen en vervalt niet, maar veroudert zichtbaar. Twee mensen die
tegelijk reageren op een kind is geen probleem; twee die allebei niets doen omdat
de ander het "geclaimd" had, wel. En een signaal dat vanzelf verdwijnt laat een
hulpvraag er onaangeroerd uitzien terwijl er iemand mee bezig is -- dus het blijft
staan en toont hoe oud het is.

AFGEHANDELD kent GEEN terugdraai. Sluiten gaat met een stevige bevestiging (geen
window.confirm, zelfde lijn als het loslaten van een ward), en leeft de vraag
daarna nog, dan wordt hij opnieuw gesteld -- een nieuwe hulpvraag. Er wordt niets
herschreven, er wordt toegevoegd. Een test bewaakt dat er geen undo() of
reopen() bestaat.

Geen nieuw protocol: de markering is een gewone directe note met een
shaer:-eigenschap, net als de zwaai en de afwezigheidsmelding. Daardoor reist hij
over de bestaande bezorging, ziet de WARD hem als bericht ("er komt iemand") en
houden de mede-guardians er staat aan over. Naar de ward en naar de andere
guardians tegelijk.

De eigen kopie wordt meteen weggeschreven, voordat er bezorgd is: het scherm van
degene die klikt hoort niet te liegen omdat een andere server traag is.

11 tests op het pure stuk. Gecontroleerd dat ze bijten: laat helpStatus altijd
"afgehandeld" zeggen en er vallen er vijf om. nl/en/de. Suite 576/576.

Niet gedekt: de weergave zelf (client-JS), en er is geen guardianship op dev om
het end-to-end te zien lopen.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/routes/guardian.js

    r792af53 ra29c8c8  
    6363    'gate_kind_setting', 'gate_kind_perRequest', 'gate_kind_handover',
    6464    'gate_unknown', 'gate_threshold', 'gate_threshold_unknown',
    65     'gate_irreversible', 'gate_waiting', 'gate_blocked', 'gate_propose'];
     65    'gate_irreversible', 'gate_waiting', 'gate_blocked', 'gate_propose',
     66    // Oppikken en afhandelen van een hulpvraag (shaer-lgo).
     67    'help_pick', 'help_close', 'help_picked_by', 'help_handled_by', 'help_handled_note',
     68    'help_close_ask', 'help_close_yes', 'help_just_now', 'help_hours', 'help_days'];
    6669  const s = Object.fromEntries(keys.map((k) => [k, i18nT(L, `guardian.${k}`)]));
    6770  s.wave = i18nT(L, 'guardian.wave');
     
    7780            emoji_json, actor_emoji_json, media_json, quote_json, embed_json
    7881     FROM ap_mentions WHERE slug = ? AND help_request = 1 ORDER BY created_at DESC LIMIT 50`
    79   ).all(site.slug).map((h) => ({
     82  ).all(site.slug);
     83  // De gedeelde staat in EEN query (shaer-lgo): wie er al op af is en of het is
     84  // afgesloten. Per kaart vragen zou hier een N+1 opleveren, en dit is precies
     85  // het scherm dat een guardian in een haast openslaat.
     86  const helpStaat = Guardianship.help.statusFor(help.map((h) => h.object_uri));
     87  const helpItems = help.map((h) => ({
    8088    ...h,
     89    // Bij twijfel OPEN. Een hulpvraag die er afgehandeld uitziet terwijl hij dat
     90    // niet is, is de gevaarlijke fout -- niet andersom.
     91    state: helpStaat.get(h.object_uri) || { open: true, pickedUpBy: [], handled: null, ageMs: null },
    8192    // The dashboard is built in the browser, so it gets the body finished: the
    8293    // same partial de Krant and Berichten use. A 🛟 often carries a screenshot
     
    123134      id: r.id, ward: r.ward_uri, proposer: r.proposer, feature: r.feature, value: !!r.value,
    124135    })),
    125     help,
     136    help: helpItems,
    126137    strings: uiStrings(L),
    127138  };
     
    336347  if (!r) return res.status(502).json({ error: 'delivery' });
    337348  res.json({ ok: true, delivered: r.delivered });
     349});
     350
     351// ── Een hulpvraag oppikken of afsluiten (shaer-lgo) ───────────────
     352// Gaat naar de WARD en naar de MEDE-GUARDIANS. De ward hoort te weten dat er
     353// iemand komt -- dat is de helft van de gerustheid -- en de anderen dat het
     354// loopt, zodat niemand denkt dat de ander het al doet.
     355//
     356// OPPIKKEN mag stapelen: twee mensen die tegelijk reageren is geen probleem.
     357// AFSLUITEN kent geen terugdraai; leeft de vraag nog, dan wordt hij opnieuw
     358// gesteld. De stevige bevestiging zit in de client, net als bij het loslaten van
     359// een ward: nooit een window.confirm.
     360router.post('/api/help/:kind', requireAuth, express.json({ limit: '2kb' }), async (req, res) => {
     361  const site = siteForUser(req);
     362  if (!site) return res.status(404).json({ error: 'no_site' });
     363  const kind = req.params.kind === 'handled' ? 'handled' : 'pickup';
     364  const noteUri = String(req.body?.note || '').trim();
     365  const wardUri = String(req.body?.ward || '').trim();
     366  if (!noteUri || !/^https?:\/\//i.test(noteUri)) return res.status(400).json({ error: 'no_note' });
     367  // Alleen over een hulpvraag van een kind dat je echt bewaakt.
     368  const isWard = Guardianship.listWards(site.slug).some((w) => w.other_uri === wardUri);
     369  if (!isWard) return res.status(403).json({ error: 'not_your_ward' });
     370
     371  const me = AP.actorId((process.env.PUBLIC_BASE_URL || '').replace(/\/+$/, ''), site.slug);
     372  // Onze eigen kopie meteen, zonder op bezorging te wachten: het scherm van
     373  // degene die klikt hoort niet te liegen omdat een andere server traag is.
     374  Guardianship.help.record(noteUri, me, kind, null);
     375
     376  const anderen = Guardianship.listGuardians(wardUri.replace(/.*\/ap\/users\//, '')) || [];
     377  const ontvangers = [wardUri, ...anderen.map((g) => g.other_uri)].filter((u) => u && u !== me);
     378  const r = await AP.deliverDirectNote(site, {
     379    recipients: ontvangers,
     380    text: kind === 'handled' ? 'Deze hulpvraag is afgehandeld.' : 'Ik kijk hiernaar.',
     381    helpMark: { kind, noteUri },
     382  }).catch(() => null);
     383  // Bezorging kan mislukken; de eigen staat staat er dan toch. Dat melden we,
     384  // want "verstuurd" zeggen terwijl het niet aankwam is hier het ergste soort
     385  // stilte.
     386  res.json({ ok: true, delivered: r ? r.delivered : 0, recipients: ontvangers.length });
    338387});
    339388
Note: See TracChangeset for help on using the changeset viewer.