| 1 | /**
|
|---|
| 2 | * Guardianship (FEP-633c) — the owner-only dashboard queues.
|
|---|
| 3 | *
|
|---|
| 4 | * Three OrderedCollections on the actor (shaer:queues), same contract as the
|
|---|
| 5 | * Shaer test daemon so the iOS/Android guardian dashboards read them as-is:
|
|---|
| 6 | * - offers: pending guardianship offers where I am a party (§3)
|
|---|
| 7 | * - follows: pending follows for my wards (§5.3) — Klonkt has no gated
|
|---|
| 8 | * follows yet, so this collection is empty for now
|
|---|
| 9 | * - wards: my wards, for the dashboard's wards list
|
|---|
| 10 | */
|
|---|
| 11 | import { GUARDIAN_RELATIONSHIP_COMPACT } from './context.js';
|
|---|
| 12 | import * as relations from './relations.js';
|
|---|
| 13 |
|
|---|
| 14 | const collection = (id, items) => ({
|
|---|
| 15 | id, type: 'OrderedCollection', totalItems: items.length, orderedItems: items,
|
|---|
| 16 | });
|
|---|
| 17 |
|
|---|
| 18 | /** Pending offers, reconstructed as Offer activities (either side). Each item
|
|---|
| 19 | * also carries the daemon-contract helper fields (shaer:ward, candidate,
|
|---|
| 20 | * needsMyAccept, iAmCandidate, …): the Shaer clients render their accept
|
|---|
| 21 | * button from those, so the shapes must match the test daemon exactly. */
|
|---|
| 22 | export function offersCollection(id, slug, me) {
|
|---|
| 23 | const items = relations.listOffers(slug).map((r) => {
|
|---|
| 24 | const ward = r.role === 'guardian' ? r.other_uri : me;
|
|---|
| 25 | const candidate = r.role === 'guardian' ? me : r.other_uri;
|
|---|
| 26 | return {
|
|---|
| 27 | id: r.offer_id || `${me}/offers/pending-${r.id}`,
|
|---|
| 28 | type: 'Offer',
|
|---|
| 29 | actor: candidate,
|
|---|
| 30 | object: {
|
|---|
| 31 | type: 'Relationship',
|
|---|
| 32 | subject: ward,
|
|---|
| 33 | relationship: GUARDIAN_RELATIONSHIP_COMPACT,
|
|---|
| 34 | object: candidate,
|
|---|
| 35 | },
|
|---|
| 36 | 'shaer:ward': ward,
|
|---|
| 37 | 'shaer:candidate': candidate,
|
|---|
| 38 | 'shaer:existingGuardians': relations.listGuardians(slug).map((g) => g.other_uri),
|
|---|
| 39 | 'shaer:acceptedBy': [],
|
|---|
| 40 | // Klonkt's flow is single-phase: the ward's Accept commits at once, so
|
|---|
| 41 | // only the ward-side owner has an action here.
|
|---|
| 42 | 'shaer:needsMyAccept': r.role === 'ward',
|
|---|
| 43 | 'shaer:readyToCommit': false,
|
|---|
| 44 | 'shaer:iAmCandidate': r.role === 'guardian',
|
|---|
| 45 | 'shaer:handle': r.other_handle || undefined,
|
|---|
| 46 | published: r.created_at,
|
|---|
| 47 | };
|
|---|
| 48 | });
|
|---|
| 49 | return collection(id, items);
|
|---|
| 50 | }
|
|---|
| 51 |
|
|---|
| 52 | /** Gated follows awaiting guardian approval — not built in Klonkt yet. */
|
|---|
| 53 | export function followsCollection(id) {
|
|---|
| 54 | return collection(id, []);
|
|---|
| 55 | }
|
|---|
| 56 |
|
|---|
| 57 | /** The guardian's wards (accepted), with cached handle for display. */
|
|---|
| 58 | export function wardsCollection(id, slug) {
|
|---|
| 59 | const items = relations.listWards(slug)
|
|---|
| 60 | .filter((r) => r.status === 'accepted')
|
|---|
| 61 | .map((r) => ({ id: r.other_uri, 'shaer:handle': r.other_handle || undefined, since: r.created_at }));
|
|---|
| 62 | return collection(id, items);
|
|---|
| 63 | }
|
|---|
| 64 |
|
|---|
| 65 | export default { offersCollection, followsCollection, wardsCollection };
|
|---|