Index: src/services/guardianship/queues.js
===================================================================
--- src/services/guardianship/queues.js	(revision e84ce323dc022a50b67b2190764a08eff6302a1b)
+++ src/services/guardianship/queues.js	(revision 1a2d8ac96c0af0189037f8acd658b19b875be8b0)
@@ -16,19 +16,35 @@
 });
 
-/** Pending offers, reconstructed as Offer activities (either side). */
+/** Pending offers, reconstructed as Offer activities (either side). Each item
+ *  also carries the daemon-contract helper fields (shaer:ward, candidate,
+ *  needsMyAccept, iAmCandidate, …): the Shaer clients render their accept
+ *  button from those, so the shapes must match the test daemon exactly. */
 export function offersCollection(id, slug, me) {
-  const items = relations.listOffers(slug).map((r) => ({
-    id: r.offer_id || undefined,
-    type: 'Offer',
-    actor: r.role === 'guardian' ? me : r.other_uri,
-    object: {
-      type: 'Relationship',
-      subject: r.role === 'guardian' ? r.other_uri : me,
-      relationship: GUARDIAN_RELATIONSHIP_COMPACT,
-      object: r.role === 'guardian' ? me : r.other_uri,
-    },
-    'shaer:handle': r.other_handle || undefined,
-    published: r.created_at,
-  }));
+  const items = relations.listOffers(slug).map((r) => {
+    const ward = r.role === 'guardian' ? r.other_uri : me;
+    const candidate = r.role === 'guardian' ? me : r.other_uri;
+    return {
+      id: r.offer_id || `${me}/offers/pending-${r.id}`,
+      type: 'Offer',
+      actor: candidate,
+      object: {
+        type: 'Relationship',
+        subject: ward,
+        relationship: GUARDIAN_RELATIONSHIP_COMPACT,
+        object: candidate,
+      },
+      'shaer:ward': ward,
+      'shaer:candidate': candidate,
+      'shaer:existingGuardians': relations.listGuardians(slug).map((g) => g.other_uri),
+      'shaer:acceptedBy': [],
+      // Klonkt's flow is single-phase: the ward's Accept commits at once, so
+      // only the ward-side owner has an action here.
+      'shaer:needsMyAccept': r.role === 'ward',
+      'shaer:readyToCommit': false,
+      'shaer:iAmCandidate': r.role === 'guardian',
+      'shaer:handle': r.other_handle || undefined,
+      published: r.created_at,
+    };
+  });
   return collection(id, items);
 }
Index: test/guardianship.test.js
===================================================================
--- test/guardianship.test.js	(revision e84ce323dc022a50b67b2190764a08eff6302a1b)
+++ test/guardianship.test.js	(revision 1a2d8ac96c0af0189037f8acd658b19b875be8b0)
@@ -77,4 +77,13 @@
   assert.equal(G.listOffers('kid').length, 1);
 
+  // The kid's offers queue carries the daemon-contract helper fields the
+  // Shaer clients render their accept button from.
+  const kidQ = G.offersCollection(`${KID}/queues/offers`, 'kid', KID);
+  assert.equal(kidQ.totalItems, 1);
+  assert.equal(kidQ.orderedItems[0]['shaer:needsMyAccept'], true);
+  assert.equal(kidQ.orderedItems[0]['shaer:iAmCandidate'], false);
+  assert.equal(kidQ.orderedItems[0]['shaer:ward'], KID);
+  assert.equal(kidQ.orderedItems[0]['shaer:candidate'], ME);
+
   // The kid accepts over C2S; the answer travels to the guardian.
   const r = await G.handleGuardianshipOutbox(kid, { type: 'Accept', object: offerId });
