Changeset 0202104 in Klonkt for src/assets/js


Ignore:
Timestamp:
07/28/2026 11:40:37 PM (6 weeks ago)
Author:
Robin Genis <roboburr@…>
Branches:
main
Children:
439f095
Parents:
6eab7e9
Message:

De gebruikerskant van 3.6: de ward ziet zijn vangnet, de guardian handelt

Drie oppervlakken rondgemaakt op de beschikbaarheid die er sinds vanmorgen
server-side in zit.

Berichten (de ward): de guardians-balk toont per guardian de buddy-list-stip
met het label erbij: beschikbaar, afwezig tot een datum, offline. Het kind ziet
de echte omvang van zijn vangnet, niet alleen de namen. Owner-only omdat de
pagina dat is.

De Guardian-PWA (de guardian): in het paneel per kind staan de mede-guardians
met dezelfde stippen; op een slapende verschijnt de bewuste, zeldzame
vervolgstap "Voorstel: loslaten bij afwezigheid", die langs dezelfde
C2S-pijplijn loopt als de Shaer-apps (Offer van shaer:Lapse, lokaal ward opent
direct, remote ward krijgt het voorstel bezorgd). Lopende lapses staan als
kaart bij de aanvragen, met Eens/Oneens over de bestaande offer-draad en de zin
die het frame bewaakt. En "Even afwezig": een week of een maand, een directe
note met shaer:away naar alle wards, lokaal direct toegepast.

Shaer (beide apps): de lapse-kaart toont stemknoppen alleen aan leden van de
set. De ward kijkt mee naar wat zijn guardians beslissen; het is daar niet de
rechter, om precies de reden uit de editor's note van 3.6.3.

Onderweg gerepareerd: parseStamp kende alleen strings, waardoor een epoch-ms
endTime als lege datum rendde ("unavailable till" zonder datum).

Changed files:
src/routes/posts.js

  • /messages geeft de guardians hun beschikbaarheid mee

src/views/pages/messages.ejs

  • de stip en het label per guardian, met de opmaak erbij

src/routes/guardian.js

  • dashboardState: mede-guardians met status per lokaal kind, plus lapses
  • POST /guardian/api/away en /guardian/api/lapse
  • de nieuwe labels in uiStrings

src/assets/js/guardian.js

  • de guardians-sectie in het paneel, de lapse-kaart, de afwezig-knoppen

src/views/pages/guardian.ejs

  • de "Even afwezig"-sectie

src/assets/css/guardian.css

  • de stippen en de lapse-kaart

src/middleware/render.js

  • parseStamp accepteert epoch ms

src/services/i18n.js

  • de labels en teksten in nl, en, de

remarks: end-to-end in de browser nagelopen op de wegwerp-database: het kind
ziet oma afwezig-tot, opa offline en guard beschikbaar; de guardian opent het
paneel, stelt de lapse voor op de slapende opa (kaart verschijnt, eigen stem
geteld), drukt "A week", en bij het kind staat guard meteen op afwezig tot
5 augustus. 276 tests groen. Niet uitgerold.

-robo
Co-Authored-By: Claude Fable 5 <noreply@…>

File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/assets/js/guardian.js

    r6eab7e9 r0202104  
    123123    return card;
    124124  }
     125  /** A running lapse (FEP-633c 3.6.3): the available co-guardians deciding
     126   *  to release a dormant one. Votes ride the same Accept/Reject wire as the
     127   *  offers; buttons appear only for set members (the ward watches, it does
     128   *  not vote). */
     129  function lapseCard(l) {
     130    var card = el('div', 'g-card lapse');
     131    card.appendChild(el('div', 'who', (T.lapse_line || '{who} has stopped answering as a guardian of {ward}.')
     132      .replace('{who}', handleOf(l.object.object)).replace('{ward}', handleOf(l.object['shaer:ward']))));
     133    card.appendChild(el('div', 'g-avlabel', (T.lapse_tally || '{n} of {need} agreed; closes {date}.')
     134      .replace('{n}', l['shaer:accepts']).replace('{need}', l['shaer:threshold'])
     135      .replace('{date}', new Date(l['shaer:closesAt']).toLocaleDateString())));
     136    var row = el('div', 'row');
     137    var inSet = (l['shaer:set'] || []).indexOf(S.me) >= 0;
     138    if (inSet && !l['shaer:myVote']) {
     139      var yes = el('button', 'small', T.lapse_agree || 'Agree');
     140      yes.addEventListener('click', function () { answer(l.id, 'accept', yes); });
     141      var no = el('button', 'quiet small', T.lapse_disagree || 'Disagree');
     142      no.addEventListener('click', function () { answer(l.id, 'reject', no); });
     143      row.appendChild(yes); row.appendChild(no);
     144    } else if (inSet) {
     145      row.appendChild(el('span', 'g-avlabel', T.voted || 'You voted'));
     146    }
     147    card.appendChild(row);
     148    card.appendChild(el('p', 'g-empty small', T.lapse_note || ''));
     149    return card;
     150  }
     151
    125152  function renderPending() {
    126153    var list = document.getElementById('pending-list');
    127154    list.textContent = '';
    128155    var offers = S.offers || [];
    129     offers.forEach(function (o) { list.appendChild(offerCard(o)); });
    130     show('pending-section', offers.length > 0);
     156    // The offers state carries the adoption offers; the lapse proposals ride
     157    // separately so a lapse never renders as an adoption.
     158    var lapses = (S.lapses || []).filter(function (l) { return l['shaer:outcome'] === 'open'; });
     159    offers.forEach(function (o) {
     160      if (o.object && o.object.type === 'shaer:Lapse') return;   // rendered below
     161      list.appendChild(offerCard(o));
     162    });
     163    lapses.forEach(function (l) { list.appendChild(lapseCard(l)); });
     164    show('pending-section', offers.length > 0 || lapses.length > 0);
    131165  }
    132166
     
    233267  }
    234268
     269  /** The availability dot (FEP-633c 3.6): buddy-list language on the
     270   *  responsibility axis. Green available, yellow declared away with an end,
     271   *  grey observed dormant (one answer restores). */
     272  function availLabel(g) {
     273    if (g.availability === 'away') {
     274      var date = g.awayUntil ? new Date(g.awayUntil).toLocaleDateString() : '?';
     275      return (T.avail_away || 'Unavailable till {date}').replace('{date}', date);
     276    }
     277    if (g.availability === 'dormant') return T.avail_dormant || 'Offline';
     278    return T.avail_available || 'Available';
     279  }
     280  function availRow(g, wardUri) {
     281    var row = el('div', 'row g-guard');
     282    var dot = el('span', 'g-avdot ' + (g.availability === 'away' ? 'is-away' : g.availability === 'dormant' ? 'is-dormant' : 'is-active'));
     283    row.appendChild(dot);
     284    row.appendChild(el('span', 'who grow', handleOf(g.uri, g.handle)));
     285    row.appendChild(el('span', 'g-avlabel', availLabel(g)));
     286    // A dormant fellow guardian without a running lapse: the deliberate,
     287    // rare next step (3.6.3). Never shown for anyone still answering.
     288    if (g.availability === 'dormant' && !g.lapse && g.uri !== S.me) {
     289      var btn = el('button', 'quiet small', T.lapse_propose || 'Propose release');
     290      btn.addEventListener('click', function () {
     291        btn.disabled = true;
     292        fetch('/guardian/api/lapse', {
     293          method: 'POST', headers: { 'Content-Type': 'application/json' },
     294          body: JSON.stringify({ ward: wardUri, target: g.uri, site: S.site }),
     295        }).then(refresh).catch(function () { btn.disabled = false; });
     296      });
     297      row.appendChild(btn);
     298    }
     299    return row;
     300  }
     301
    235302  function wardPanel(w) {
    236303    var uri = w.other_uri;
     
    244311    set.appendChild(setRow);
    245312    panel.appendChild(set);
     313
     314    // The fellow guardians of this child, with availability (3.6). For a
     315    // ward on another server the states live there, and saying so honestly
     316    // beats guessing.
     317    var gsec = el('div', 'g-panel-sec');
     318    gsec.appendChild(el('h3', null, T.panel_guards || 'Guardians of this child'));
     319    if (w.guardians && w.guardians.length) {
     320      w.guardians.forEach(function (g) { gsec.appendChild(availRow(g, uri)); });
     321    } else {
     322      gsec.appendChild(el('p', 'g-empty small', T.panel_guards_remote || ''));
     323    }
     324    panel.appendChild(gsec);
    246325
    247326    sectionInto(panel, T.panel_follow || 'Follow requests',
     
    314393    });
    315394    show('wards-empty', wards.length === 0);
    316   }
     395    // Step away (3.6.1) only means something with wards to tell.
     396    show('away-section', wards.length > 0);
     397  }
     398
     399  // ── 4b. Step away (FEP-633c 3.6.1) ─────────────────────────────────────
     400  function declareAway(days, btn) {
     401    btn.disabled = true;
     402    fetch('/guardian/api/away', {
     403      method: 'POST', headers: { 'Content-Type': 'application/json' },
     404      body: JSON.stringify({ days: days, site: S.site }),
     405    }).then(function (r) { return r.json(); })
     406      .then(function (j) {
     407        btn.disabled = false;
     408        var msg = document.getElementById('away-msg');
     409        msg.hidden = false;
     410        if (j && j.ok) {
     411          msg.className = 'g-msg';
     412          msg.textContent = (T.away_done || 'Your wards know you are unavailable until {date}.')
     413            .replace('{date}', new Date(j.until).toLocaleDateString());
     414        } else {
     415          msg.className = 'g-msg err';
     416          msg.textContent = (j && j.error) || (T.failed || 'failed');
     417        }
     418      })
     419      .catch(function () { btn.disabled = false; });
     420  }
     421  var awayWeek = document.getElementById('away-week');
     422  var awayMonth = document.getElementById('away-month');
     423  if (awayWeek) awayWeek.addEventListener('click', function () { declareAway(7, awayWeek); });
     424  if (awayMonth) awayMonth.addEventListener('click', function () { declareAway(30, awayMonth); });
    317425
    318426  function sendWave(uri, btn) {
Note: See TracChangeset for help on using the changeset viewer.