Index: src/services/guardianship/delivery.js
===================================================================
--- src/services/guardianship/delivery.js	(revision 5c373b8e846c6510a176f2fa06bfe3e2b6753102)
+++ src/services/guardianship/delivery.js	(revision e62f65d51ce6e100e8c44bd84a06c8731f893a03)
@@ -40,5 +40,5 @@
 // guardian on any instance receives it as a private mention (the ward
 // call-for-help path).
-export async function deliverDirectNote(site, { recipients, text, language, inReplyTo, attachments, helpRequest }) {
+export async function deliverDirectNote(site, { recipients, text, language, inReplyTo, attachments, helpRequest, wave }) {
   const { actorId, fetchActor, deriveHandle, escHtml, linkUrls, linkHashtags,
           getOutboxRow, buildReplyNote, AP_CONTEXT, getOrCreateKeys, deliver, enqueueDelivery } = deps;
@@ -70,7 +70,7 @@
     .map((a) => ({ url: a.url, mediaType: String(a.mediaType), name: String(a.name || '').slice(0, 120) }));
   const id = crypto.randomUUID();
-  db.prepare(`INSERT INTO ap_outbox (id, site_slug, post_id, post_slug, in_reply_to, to_actor, to_handle, content, language, attachments, visibility, to_actors, help_request, created_at)
-              VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,CURRENT_TIMESTAMP)`)
-    .run(id, site.slug, '', null, inReplyTo || null, resolved[0].uri, resolved[0].handle, content, lang, media.length ? JSON.stringify(media) : null, 'direct', JSON.stringify(resolved.map((r) => r.uri)), helpRequest ? 1 : 0);
+  db.prepare(`INSERT INTO ap_outbox (id, site_slug, post_id, post_slug, in_reply_to, to_actor, to_handle, content, language, attachments, visibility, to_actors, help_request, wave, created_at)
+              VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,CURRENT_TIMESTAMP)`)
+    .run(id, site.slug, '', null, inReplyTo || null, resolved[0].uri, resolved[0].handle, content, lang, media.length ? JSON.stringify(media) : null, 'direct', JSON.stringify(resolved.map((r) => r.uri)), helpRequest ? 1 : 0, wave ? 1 : 0);
   const row = getOutboxRow(id);
   const note = buildReplyNote(base, site, row);
Index: src/services/guardianship/index.js
===================================================================
--- src/services/guardianship/index.js	(revision 5c373b8e846c6510a176f2fa06bfe3e2b6753102)
+++ src/services/guardianship/index.js	(revision e62f65d51ce6e100e8c44bd84a06c8731f893a03)
@@ -16,5 +16,5 @@
  */
 export { SHAER_CONTEXT, GUARDIAN_RELATIONSHIP, GUARDIAN_RELATIONSHIP_COMPACT, isGuardianRelationship } from './context.js';
-export { helpRequestProps, isHelpRequest } from './notes.js';
+export { helpRequestProps, isHelpRequest, waveProps, isWave } from './notes.js';
 export { wireDelivery, c2sVisibility, deliverDirectNote } from './delivery.js';
 export { wireHandshake, handleOutbox as handleGuardianshipOutbox, handleInbox as handleGuardianshipInbox, parseRelationship } from './handshake.js';
Index: src/services/guardianship/notes.js
===================================================================
--- src/services/guardianship/notes.js	(revision 5c373b8e846c6510a176f2fa06bfe3e2b6753102)
+++ src/services/guardianship/notes.js	(revision e62f65d51ce6e100e8c44bd84a06c8731f893a03)
@@ -18,3 +18,16 @@
 }
 
-export default { helpRequestProps, isHelpRequest };
+/** shaer:wave: a gentle "thinking of you" from a guardian to its ward. A
+ *  private nudge, never a feed post; non-shaer clients see a plain DM. */
+export function waveProps(post) {
+  return (post && post.visibility === 'direct' && post.wave)
+    ? { 'shaer:wave': true }
+    : {};
+}
+
+/** True when an incoming note is a wave. */
+export function isWave(object) {
+  return !!object && (object['shaer:wave'] === true || object.wave === true);
+}
+
+export default { helpRequestProps, isHelpRequest, waveProps, isWave };
