<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom"><title>Breathe</title><id>https://breatheoutbreathe.in/feed.xml</id><subtitle>Recent Posts</subtitle><updated>2026-10-06T20:13:28Z</updated><link href="https://breatheoutbreathe.in/feed.xml" rel="self" /><link href="https://breatheoutbreathe.in" /><entry><title>Subjective Delegation XEP</title><id>https://breatheoutbreathe.in/posts/subjective-delegation-xep.html</id><author><name>Joseph Turner</name><email>contact@breatheoutbreathe.in</email></author><updated>2026-09-30T12:00:00Z</updated><link href="https://breatheoutbreathe.in/posts/subjective-delegation-xep.html" rel="alternate" /><content type="html">&lt;p&gt;I have been interested in subjective moderation for years, but I am
not very familiar with the inner workings of XMPP.  Please &lt;a href=&quot;/contact/index.html&quot;&gt;contact
me&lt;/a&gt; with feedback on this XEP draft.  Thank you!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/static/etc/2026-09-23-subjective-delegation-xep-draft.md&quot;&gt;Download markdown&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://git.sr.ht/~breatheoutbreathein/xep-subjective-delegation&quot;&gt;Git repo&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;&lt;h1&gt;XEP-XXXX: Subjective Delegation for Multi-User Chat&lt;/h1&gt;
&lt;p&gt;Status: Experimental&lt;br /&gt;Type: Standards Track&lt;br /&gt;Short Name: subjective-delegation&lt;br /&gt;Namespace: &lt;code&gt;urn:xmpp:subjective-delegation:0&lt;/code&gt;&lt;br /&gt;Dependencies: XEP-0030, XEP-0045, XEP-0060, XEP-0359, XEP-0421&lt;br /&gt;Optional: XEP-0004, XEP-0128, XEP-0163, XEP-0313, XEP-0334&lt;br /&gt;Author: Joseph Turner&lt;/p&gt;
&lt;h2&gt;1. Abstract&lt;/h2&gt;
&lt;p&gt;This specification defines a PubSub-based protocol for subjective
delegation in XMPP Multi-User Chat rooms.&lt;/p&gt;
&lt;p&gt;The generic protocol defines a room-scoped graph model in which
participants publish decisions, delegate decision evaluation to other
participants, and locally exclude participants from delegated
traversal.&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;&quot;&gt;A: What's your favorite line from Romeo and Juliet?
B: That's too OT.  The current topic is XEP design...
A: Alas!  There is no world without Verona walls.
B: I pray thee, leave me to myself.
   *B mutes A*
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This specification also defines one initial capability: &lt;code&gt;mute&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;mute&lt;/code&gt; capability affects only local client rendering. It does not
remove, ban, kick, moderate, retract, or silence any participant.&lt;/p&gt;
&lt;h2&gt;2. Introduction&lt;/h2&gt;
&lt;p&gt;Multi-User Chat moderation is objective and room-wide. A moderator’s
action generally affects the room state observed by all participants.&lt;/p&gt;
&lt;p&gt;By contrast, each participant can use subjectively delegated &lt;code&gt;mute&lt;/code&gt; to
compute their own local view of a room by combining their own &lt;code&gt;mute&lt;/code&gt;
decisions with &lt;code&gt;mute&lt;/code&gt; decisions imported from delegates.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Alice mutes Bob.&lt;/li&gt;
&lt;li&gt;Alice delegates mute decisions to Carol.&lt;/li&gt;
&lt;li&gt;Carol mutes Dave.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Result:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Alice’s client locally suppresses Bob’s and Dave’s messages.&lt;/li&gt;
&lt;li&gt;Carol’s client locally suppresses Dave’s messages.&lt;/li&gt;
&lt;li&gt;Other participants are unaffected.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Participants can recursively gather decisions from delegate's
delegates.  Participants can also prune delegates from the graph with
exclusion rules.&lt;/p&gt;
&lt;p&gt;A richer example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Alice delegates mute decisions to Carol recursively.&lt;/li&gt;
&lt;li&gt;Carol mutes Bob.&lt;/li&gt;
&lt;li&gt;Carol delegates mute decisions to Dana.&lt;/li&gt;
&lt;li&gt;Carol also delegates mute decisions to Eve.&lt;/li&gt;
&lt;li&gt;Dana mutes Fred.&lt;/li&gt;
&lt;li&gt;Eve mutes Mallory.&lt;/li&gt;
&lt;li&gt;Alice excludes Dana as a delegate.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Result on Alice's client:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Alice’s client locally suppresses Bob’s and Mallory's messages.&lt;/li&gt;
&lt;li&gt;Alice’s client does not suppress Dave’s messages through Dana.&lt;/li&gt;
&lt;li&gt;Dana and Dana’s delegates are not traversed for Alice.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This specification uses PubSub for public room graph state and update
notifications. This allows clients joining a room to fetch current
graph state without reconstructing it from MUC message history.&lt;/p&gt;
&lt;p&gt;PEP may be used for private multi-device synchronization, but PEP is
not used as the authoritative source for public room graph state.&lt;/p&gt;
&lt;h2&gt;3. Generic Model&lt;/h2&gt;
&lt;p&gt;Subjective delegation is modeled as a directed graph of edges.&lt;/p&gt;
&lt;p&gt;Each edge has:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;an actor,&lt;/li&gt;
&lt;li&gt;a type,&lt;/li&gt;
&lt;li&gt;a capability,&lt;/li&gt;
&lt;li&gt;a room,&lt;/li&gt;
&lt;li&gt;and, usually, a target.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The generic protocol defines edge types and graph traversal behavior.
Individual capabilities define the meaning of their decisions.&lt;/p&gt;
&lt;h2&gt;4. Concepts&lt;/h2&gt;
&lt;h3&gt;4.1 Actor&lt;/h3&gt;
&lt;p&gt;The identity that creates an edge.&lt;/p&gt;
&lt;h3&gt;4.2 Target&lt;/h3&gt;
&lt;p&gt;The object affected by an edge.&lt;/p&gt;
&lt;p&gt;For &lt;code&gt;delegate&lt;/code&gt; and &lt;code&gt;exclude&lt;/code&gt; edges, the target is an identity.&lt;/p&gt;
&lt;p&gt;For decision edges, the target type is capability-specific. A decision
may target an identity, a message, a thread, or another object defined
by the capability.&lt;/p&gt;
&lt;h3&gt;4.3 Capability&lt;/h3&gt;
&lt;p&gt;A capability is a decision namespace.&lt;/p&gt;
&lt;p&gt;This specification defines one capability:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;mute&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Future specifications may define additional capabilities.&lt;/p&gt;
&lt;p&gt;A capability defines:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;valid decision actions,&lt;/li&gt;
&lt;li&gt;valid target types,&lt;/li&gt;
&lt;li&gt;whether decisions may be public, private, or both,&lt;/li&gt;
&lt;li&gt;how imported decisions are evaluated,&lt;/li&gt;
&lt;li&gt;how local conflicts are resolved.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The generic &lt;code&gt;&amp;lt;edge/&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;actor/&amp;gt;&lt;/code&gt;, and &lt;code&gt;&amp;lt;target/&amp;gt;&lt;/code&gt; elements are defined
in the generic namespace:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;text&quot;&gt;urn:xmpp:subjective-delegation:0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Capabilities MAY define additional semantics for actions and targets.&lt;/p&gt;
&lt;p&gt;If a capability requires additional XML payload beyond the generic
attributes and elements, that payload SHOULD use a capability-specific
XML namespace.&lt;/p&gt;
&lt;h3&gt;4.4 Decision Edge&lt;/h3&gt;
&lt;p&gt;A decision edge expresses the actor’s decision within a capability.&lt;/p&gt;
&lt;p&gt;Example: Alice publishes a &lt;code&gt;mute&lt;/code&gt; decision suppressing Bob.&lt;/p&gt;
&lt;h3&gt;4.5 Delegate Edge&lt;/h3&gt;
&lt;p&gt;A &lt;code&gt;delegate&lt;/code&gt; edge expresses that the actor imports another identity’s
public decisions for a capability.&lt;/p&gt;
&lt;p&gt;A &lt;code&gt;delegate&lt;/code&gt; edge may also permit recursive traversal through the
target’s public &lt;code&gt;delegate&lt;/code&gt; edges. Recursion depth is controlled by the
&lt;code&gt;hops&lt;/code&gt; attribute.&lt;/p&gt;
&lt;h3&gt;4.6 Exclude Edge&lt;/h3&gt;
&lt;p&gt;An &lt;code&gt;exclude&lt;/code&gt; edge expresses that the actor excludes the target identity
from delegated traversal for a capability.&lt;/p&gt;
&lt;p&gt;For conceptual and computational simplicity, only positive additions
to the delegation graph can be imported from delegates.  Therefore,
&lt;code&gt;exclude&lt;/code&gt; edges are always local, and MUST NOT be imported through
delegation.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Alice delegates mute decisions to Carol recursively.&lt;/li&gt;
&lt;li&gt;Carol delegates mute decisions to Dana.&lt;/li&gt;
&lt;li&gt;Alice excludes Dana.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Result:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Alice imports Carol’s public mute decisions.&lt;/li&gt;
&lt;li&gt;Alice does not import Dana’s mute decisions through Carol.&lt;/li&gt;
&lt;li&gt;Alice's client does not traverse Dana’s delegates.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4.7 Public Edge&lt;/h3&gt;
&lt;p&gt;A public edge is stored in the room public graph node and visible
according to that node’s access model.&lt;/p&gt;
&lt;h3&gt;4.8 Private Edge&lt;/h3&gt;
&lt;p&gt;A private edge is stored locally or in private PEP storage visible
only to the actor’s clients.&lt;/p&gt;
&lt;p&gt;Private edges are never imported through delegation by other users.&lt;/p&gt;
&lt;h2&gt;5. Edge Types&lt;/h2&gt;
&lt;p&gt;This specification defines three generic edge types:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;decision&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;delegate&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;exclude&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Generic edge attributes:&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Attribute&lt;/th&gt;
      &lt;th&gt;Meaning&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code&gt;type&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code&gt;decision&lt;/code&gt;, &lt;code&gt;delegate&lt;/code&gt;, or &lt;code&gt;exclude&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code&gt;capability&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Capability name&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code&gt;room&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Room JID&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Decision edges additionally have:&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Attribute&lt;/th&gt;
      &lt;th&gt;Meaning&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code&gt;action&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Capability-specific action&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code&gt;delegate&lt;/code&gt; edges additionally have:&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Attribute&lt;/th&gt;
      &lt;th&gt;Meaning&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code&gt;hops&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Non-negative integer or &lt;code&gt;unbounded&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;6. Identity Model&lt;/h2&gt;
&lt;p&gt;This protocol supports two identity forms:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;bare JIDs,&lt;/li&gt;
&lt;li&gt;XEP-0421 stable occupant IDs.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It does not support:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;MUC nicknames,&lt;/li&gt;
&lt;li&gt;MUC occupant JIDs,&lt;/li&gt;
&lt;li&gt;full JIDs,&lt;/li&gt;
&lt;li&gt;temporary session identifiers,&lt;/li&gt;
&lt;li&gt;display names,&lt;/li&gt;
&lt;li&gt;avatars.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;6.1 Non-anonymous MUCs&lt;/h3&gt;
&lt;p&gt;In non-anonymous MUCs, implementations MAY use bare JIDs or XEP-0421
occupant IDs to identify actors and identity targets.&lt;/p&gt;
&lt;h3&gt;6.2 Semi-anonymous MUCs&lt;/h3&gt;
&lt;p&gt;In semi-anonymous MUCs, implementations MUST use XEP-0421 occupant IDs
only.&lt;/p&gt;
&lt;p&gt;Protocol payloads for semi-anonymous rooms MUST NOT contain actor or
target bare JIDs, even if the publishing client knows them.&lt;/p&gt;
&lt;h3&gt;6.3 Anonymous MUCs&lt;/h3&gt;
&lt;p&gt;In anonymous MUCs, implementations MUST use XEP-0421 occupant IDs
only.&lt;/p&gt;
&lt;p&gt;If a participant has no identity appropriate for the room’s anonymity
mode, that participant is not addressable by identity through this
protocol.&lt;/p&gt;
&lt;h3&gt;6.4 Room Scoping&lt;/h3&gt;
&lt;p&gt;Occupant IDs are scoped to a room. The occupant ID alone is not
globally meaningful.&lt;/p&gt;
&lt;p&gt;An implementation MUST interpret an occupant ID together with the
&lt;code&gt;room&lt;/code&gt; attribute of the edge.&lt;/p&gt;
&lt;h2&gt;7. PubSub Model&lt;/h2&gt;
&lt;p&gt;There are two storage locations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;room public graph storage,&lt;/li&gt;
&lt;li&gt;private user graph storage.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This specification uses XEP-0060 PubSub as the storage and
notification substrate for room public graph state.&lt;/p&gt;
&lt;p&gt;Private graph storage may be implemented using:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a private PEP node,&lt;/li&gt;
&lt;li&gt;local client storage.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;PEP MUST NOT be used as the authoritative source for public room graph
edges. PEP nodes are discoverable by bare JID and are therefore not
suitable for authoritative public graph state for anonymous and
semi-anonymous rooms.&lt;/p&gt;
&lt;h2&gt;8. Room Public Graph Node&lt;/h2&gt;
&lt;p&gt;Each supported room SHOULD advertise a public graph node that stores
public edges relevant to that room.&lt;/p&gt;
&lt;p&gt;Example node name:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;text&quot;&gt;urn:xmpp:subjective-delegation:0/rooms/room@example.org/public
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The exact node name is implementation-defined, but it MUST be
discoverable.&lt;/p&gt;
&lt;p&gt;The public graph node stores only public edges. Capability
specifications define which edges may be public.&lt;/p&gt;
&lt;h3&gt;8.1 Node Requirements&lt;/h3&gt;
&lt;p&gt;The room public graph node MUST support:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;persistent items,&lt;/li&gt;
&lt;li&gt;item IDs,&lt;/li&gt;
&lt;li&gt;item retraction,&lt;/li&gt;
&lt;li&gt;payload delivery,&lt;/li&gt;
&lt;li&gt;event notifications,&lt;/li&gt;
&lt;li&gt;access control.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The node MUST be configured with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;persistent items enabled,&lt;/li&gt;
&lt;li&gt;payload delivery enabled,&lt;/li&gt;
&lt;li&gt;event notifications enabled.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The public graph node’s access model SHOULD match the room’s effective access
policy. For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;open room: readable by anyone or by current occupants,&lt;/li&gt;
&lt;li&gt;members-only room: readable by members, admins, and owners,&lt;/li&gt;
&lt;li&gt;otherwise restricted room: readable only by entities authorized to access the room.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;9. Private Graph Storage&lt;/h2&gt;
&lt;p&gt;Private graph storage contains edges used only by the actor’s own
clients.&lt;/p&gt;
&lt;p&gt;Private edges MUST NOT be shared with the room public graph node.&lt;/p&gt;
&lt;p&gt;Private storage MUST be readable and writable only by the owning user
or resources authorized by that user.&lt;/p&gt;
&lt;p&gt;Private edges belonging to another actor MUST NOT be imported through
delegation.&lt;/p&gt;
&lt;p&gt;If PEP is used for private synchronization, private edges are stored
as PubSub items using the same edge payload format.  If private graph
state is stored locally, there is no PubSub &lt;code&gt;&amp;lt;item/&amp;gt;&lt;/code&gt; wrapper.&lt;/p&gt;
&lt;h2&gt;10. Discovery&lt;/h2&gt;
&lt;p&gt;A room supporting this protocol MUST advertise protocol support using XEP-0030 service discovery:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;text&quot;&gt;urn:xmpp:subjective-delegation:0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A room supporting this protocol MUST advertise the PubSub service and
public graph node using XEP-0128 extended service discovery.  The data
form advertises the concrete PubSub location where public graph state
is stored.&lt;/p&gt;
&lt;p&gt;Example disco response:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;xml&quot;&gt;&amp;lt;iq xmlns=&amp;quot;jabber:client&amp;quot;
    type=&amp;quot;result&amp;quot;
    from=&amp;quot;room@example.org&amp;quot;
    to=&amp;quot;alice@example.com/laptop&amp;quot;
    id=&amp;quot;disco1&amp;quot;&amp;gt;
  &amp;lt;query xmlns=&amp;quot;http://jabber.org/protocol/disco#info&amp;quot;&amp;gt;
    &amp;lt;feature var=&amp;quot;urn:xmpp:subjective-delegation:0&amp;quot;/&amp;gt;
    &amp;lt;feature var=&amp;quot;urn:xmpp:occupant-id:0&amp;quot;/&amp;gt;
    &amp;lt;x xmlns=&amp;quot;jabber:x:data&amp;quot; type=&amp;quot;result&amp;quot;&amp;gt;
      &amp;lt;field var=&amp;quot;FORM_TYPE&amp;quot; type=&amp;quot;hidden&amp;quot;&amp;gt;
        &amp;lt;value&amp;gt;urn:xmpp:subjective-delegation:0#disco&amp;lt;/value&amp;gt;
      &amp;lt;/field&amp;gt;
      &amp;lt;field var=&amp;quot;pubsub-service&amp;quot;&amp;gt;
        &amp;lt;value&amp;gt;pubsub.example.org&amp;lt;/value&amp;gt;
      &amp;lt;/field&amp;gt;
      &amp;lt;field var=&amp;quot;public-node&amp;quot;&amp;gt;
        &amp;lt;value&amp;gt;urn:xmpp:subjective-delegation:0/rooms/room@example.org/public&amp;lt;/value&amp;gt;
      &amp;lt;/field&amp;gt;
    &amp;lt;/x&amp;gt;
  &amp;lt;/query&amp;gt;
&amp;lt;/iq&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;11. Edge Payload Format&lt;/h2&gt;
&lt;p&gt;Edges are XML payloads, usually carried inside PubSub &lt;code&gt;&amp;lt;item/&amp;gt;&lt;/code&gt;
elements.&lt;/p&gt;
&lt;p&gt;The edge element is qualified by:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;text&quot;&gt;urn:xmpp:subjective-delegation:0
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;11.1 Identity Elements&lt;/h3&gt;
&lt;p&gt;Actors and identity targets are represented using either &lt;code&gt;jid&lt;/code&gt; or
&lt;code&gt;occupant-id&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;xml&quot;&gt;&amp;lt;actor jid=&amp;quot;alice@example.com&amp;quot;/&amp;gt;
&amp;lt;actor occupant-id=&amp;quot;a1&amp;quot;/&amp;gt;
&amp;lt;target jid=&amp;quot;bob@example.com&amp;quot;/&amp;gt;
&amp;lt;target occupant-id=&amp;quot;b2&amp;quot;/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;occupant-id&lt;/code&gt; MUST be used in anonymous and semi-anonymous rooms.  An
identity element MUST NOT contain both &lt;code&gt;jid&lt;/code&gt; and &lt;code&gt;occupant-id&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;11.2 Direct Decision Example&lt;/h3&gt;
&lt;p&gt;Alice publishes a public decision in a non-anonymous room:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;xml&quot;&gt;&amp;lt;item xmlns=&amp;quot;http://jabber.org/protocol/pubsub&amp;quot; id=&amp;quot;edge-1&amp;quot;&amp;gt;
  &amp;lt;edge xmlns=&amp;quot;urn:xmpp:subjective-delegation:0&amp;quot;
        type=&amp;quot;decision&amp;quot;
        capability=&amp;quot;mute&amp;quot;
        action=&amp;quot;suppress&amp;quot;
        room=&amp;quot;room@example.org&amp;quot;&amp;gt;
    &amp;lt;actor jid=&amp;quot;alice@example.com&amp;quot;/&amp;gt;
    &amp;lt;target jid=&amp;quot;bob@example.com&amp;quot;/&amp;gt;
  &amp;lt;/edge&amp;gt;
&amp;lt;/item&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;11.3 Occupant-ID Decision Example&lt;/h3&gt;
&lt;p&gt;Occupant &lt;code&gt;a1&lt;/code&gt; publishes a decision targeting occupant &lt;code&gt;b2&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;xml&quot;&gt;&amp;lt;item xmlns=&amp;quot;http://jabber.org/protocol/pubsub&amp;quot; id=&amp;quot;edge-2&amp;quot;&amp;gt;
  &amp;lt;edge xmlns=&amp;quot;urn:xmpp:subjective-delegation:0&amp;quot;
        type=&amp;quot;decision&amp;quot;
        capability=&amp;quot;mute&amp;quot;
        action=&amp;quot;suppress&amp;quot;
        room=&amp;quot;room@example.org&amp;quot;&amp;gt;
    &amp;lt;actor occupant-id=&amp;quot;a1&amp;quot;/&amp;gt;
    &amp;lt;target occupant-id=&amp;quot;b2&amp;quot;/&amp;gt;
  &amp;lt;/edge&amp;gt;
&amp;lt;/item&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;11.4 Delegate Edge Example&lt;/h3&gt;
&lt;p&gt;Alice delegates mute decisions to Carol, importing only Carol’s direct
public decisions as indicated by &lt;code&gt;hops=&amp;quot;0&amp;quot;&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;xml&quot;&gt;&amp;lt;item xmlns=&amp;quot;http://jabber.org/protocol/pubsub&amp;quot; id=&amp;quot;edge-3&amp;quot;&amp;gt;
  &amp;lt;edge xmlns=&amp;quot;urn:xmpp:subjective-delegation:0&amp;quot;
        type=&amp;quot;delegate&amp;quot;
        capability=&amp;quot;mute&amp;quot;
        hops=&amp;quot;0&amp;quot;
        room=&amp;quot;room@example.org&amp;quot;&amp;gt;
    &amp;lt;actor jid=&amp;quot;alice@example.com&amp;quot;/&amp;gt;
    &amp;lt;target jid=&amp;quot;carol@example.com&amp;quot;/&amp;gt;
  &amp;lt;/edge&amp;gt;
&amp;lt;/item&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;11.5 Recursive Delegate Edge Example&lt;/h3&gt;
&lt;p&gt;Alice delegates mute decisions to Carol and permits recursive
traversal.  In the following example, &lt;code&gt;hops=&amp;quot;2&amp;quot;&lt;/code&gt; means that Alice's
client will aggregate decisions from Carol, Carol's delegates, and
Carol's delegate's delegates:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;xml&quot;&gt;&amp;lt;item xmlns=&amp;quot;http://jabber.org/protocol/pubsub&amp;quot; id=&amp;quot;edge-4&amp;quot;&amp;gt;
  &amp;lt;edge xmlns=&amp;quot;urn:xmpp:subjective-delegation:0&amp;quot;
        type=&amp;quot;delegate&amp;quot;
        capability=&amp;quot;mute&amp;quot;
        hops=&amp;quot;2&amp;quot;
        room=&amp;quot;room@example.org&amp;quot;&amp;gt;
    &amp;lt;actor jid=&amp;quot;alice@example.com&amp;quot;/&amp;gt;
    &amp;lt;target jid=&amp;quot;carol@example.com&amp;quot;/&amp;gt;
  &amp;lt;/edge&amp;gt;
&amp;lt;/item&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alternatively, use &lt;code&gt;hops=&amp;quot;unbounded&amp;quot;&lt;/code&gt; to indicate that recursion
continues indefinitely.&lt;/p&gt;
&lt;h3&gt;11.6 Private Exclude Edge Example&lt;/h3&gt;
&lt;p&gt;Alice excludes Dana from mute delegation traversal:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;xml&quot;&gt;&amp;lt;item xmlns=&amp;quot;http://jabber.org/protocol/pubsub&amp;quot; id=&amp;quot;edge-6&amp;quot;&amp;gt;
  &amp;lt;edge xmlns=&amp;quot;urn:xmpp:subjective-delegation:0&amp;quot;
        type=&amp;quot;exclude&amp;quot;
        capability=&amp;quot;mute&amp;quot;
        room=&amp;quot;room@example.org&amp;quot;&amp;gt;
    &amp;lt;actor jid=&amp;quot;alice@example.com&amp;quot;/&amp;gt;
    &amp;lt;target jid=&amp;quot;dana@example.com&amp;quot;/&amp;gt;
  &amp;lt;/edge&amp;gt;
&amp;lt;/item&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This edge is private and MUST NOT be published to the room public graph
node.&lt;/p&gt;
&lt;h3&gt;11.7 Private Decision Example&lt;/h3&gt;
&lt;p&gt;The protocol for a particular capability may specify that some
decisions can be private.  For example, the &lt;code&gt;mute&lt;/code&gt; capability
specifies that &lt;code&gt;&amp;quot;allow&amp;quot;&lt;/code&gt; decisions are always private.  In the
following example, Alice privately allows Bob:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;xml&quot;&gt;&amp;lt;item xmlns=&amp;quot;http://jabber.org/protocol/pubsub&amp;quot; id=&amp;quot;edge-5&amp;quot;&amp;gt;
  &amp;lt;edge xmlns=&amp;quot;urn:xmpp:subjective-delegation:0&amp;quot;
        type=&amp;quot;decision&amp;quot;
        capability=&amp;quot;mute&amp;quot;
        action=&amp;quot;allow&amp;quot;
        room=&amp;quot;room@example.org&amp;quot;&amp;gt;
    &amp;lt;actor jid=&amp;quot;alice@example.com&amp;quot;/&amp;gt;
    &amp;lt;target jid=&amp;quot;bob@example.com&amp;quot;/&amp;gt;
  &amp;lt;/edge&amp;gt;
&amp;lt;/item&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This example assumes private storage uses PEP. If stored locally, the
&lt;code&gt;&amp;lt;edge/&amp;gt;&lt;/code&gt; payload is stored without a PubSub &lt;code&gt;&amp;lt;item/&amp;gt;&lt;/code&gt; wrapper.&lt;/p&gt;
&lt;h2&gt;12. Hops Semantics&lt;/h2&gt;
&lt;p&gt;A &lt;code&gt;delegate&lt;/code&gt; edge imports the target’s direct public decisions for the
named capability.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;hops&lt;/code&gt; attribute controls whether the target’s public delegate
edges are also followed.&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;&lt;code&gt;hops&lt;/code&gt;&lt;/th&gt;
      &lt;th&gt;Meaning&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Import target’s direct public decisions only&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Also import target’s delegates' decisions&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code&gt;2&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Also import target's delegates' delegates' decisions&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code&gt;unbounded&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Continue importing recursively, subject to implementation limits&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;13. Publishing Public Edges&lt;/h2&gt;
&lt;p&gt;A client publishes a public edge using XEP-0060 publish.&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;xml&quot;&gt;&amp;lt;iq xmlns=&amp;quot;jabber:client&amp;quot;
    type=&amp;quot;set&amp;quot;
    from=&amp;quot;alice@example.com/laptop&amp;quot;
    to=&amp;quot;pubsub.example.org&amp;quot;
    id=&amp;quot;pub1&amp;quot;&amp;gt;
  &amp;lt;pubsub xmlns=&amp;quot;http://jabber.org/protocol/pubsub&amp;quot;&amp;gt;
    &amp;lt;publish node=&amp;quot;urn:xmpp:subjective-delegation:0/rooms/room@example.org/public&amp;quot;&amp;gt;
      &amp;lt;item id=&amp;quot;edge-1&amp;quot;&amp;gt;
        &amp;lt;edge xmlns=&amp;quot;urn:xmpp:subjective-delegation:0&amp;quot;
              type=&amp;quot;decision&amp;quot;
              capability=&amp;quot;mute&amp;quot;
              action=&amp;quot;suppress&amp;quot;
              room=&amp;quot;room@example.org&amp;quot;&amp;gt;
          &amp;lt;actor jid=&amp;quot;alice@example.com&amp;quot;/&amp;gt;
          &amp;lt;target jid=&amp;quot;bob@example.com&amp;quot;/&amp;gt;
        &amp;lt;/edge&amp;gt;
      &amp;lt;/item&amp;gt;
    &amp;lt;/publish&amp;gt;
  &amp;lt;/pubsub&amp;gt;
&amp;lt;/iq&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The PubSub service then sends normal PubSub event notifications to
subscribers.&lt;/p&gt;
&lt;h2&gt;14. Revocation&lt;/h2&gt;
&lt;p&gt;Edges are stateful. Revocation is performed by retracting the PubSub
item.&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;xml&quot;&gt;&amp;lt;iq xmlns=&amp;quot;jabber:client&amp;quot;
    type=&amp;quot;set&amp;quot;
    from=&amp;quot;alice@example.com/laptop&amp;quot;
    to=&amp;quot;pubsub.example.org&amp;quot;
    id=&amp;quot;retract1&amp;quot;&amp;gt;
  &amp;lt;pubsub xmlns=&amp;quot;http://jabber.org/protocol/pubsub&amp;quot;&amp;gt;
    &amp;lt;retract node=&amp;quot;urn:xmpp:subjective-delegation:0/rooms/room@example.org/public&amp;quot;&amp;gt;
      &amp;lt;item id=&amp;quot;edge-1&amp;quot;/&amp;gt;
    &amp;lt;/retract&amp;gt;
  &amp;lt;/pubsub&amp;gt;
&amp;lt;/iq&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After retraction, clients MUST remove the edge from their local graph.&lt;/p&gt;
&lt;p&gt;Implementations SHOULD use stable item IDs. An item ID SHOULD identify
exactly one active edge.&lt;/p&gt;
&lt;h2&gt;15. Notifications and Synchronization&lt;/h2&gt;
&lt;p&gt;Subscribers receive updates via normal PubSub event notifications.&lt;/p&gt;
&lt;p&gt;Clients SHOULD fetch current node state:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;on login,&lt;/li&gt;
&lt;li&gt;on reconnect,&lt;/li&gt;
&lt;li&gt;on room join,&lt;/li&gt;
&lt;li&gt;after missed notifications are suspected.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;PubSub notifications are not authoritative. The PubSub item set is
authoritative.&lt;/p&gt;
&lt;p&gt;Services MAY include XEP-0334 &lt;code&gt;no-store&lt;/code&gt; hints on PubSub notification
messages because durable state is stored in PubSub items rather than
notification messages.&lt;/p&gt;
&lt;h2&gt;16. Generic Delegation Evaluation&lt;/h2&gt;
&lt;p&gt;The following Scheme-like pseudocode defines behavior, not implementation language.&lt;/p&gt;
&lt;p&gt;For local actor &lt;code&gt;U&lt;/code&gt;, room &lt;code&gt;R&lt;/code&gt;, and capability &lt;code&gt;C&lt;/code&gt;, evaluation uses:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;decisions authored by &lt;code&gt;U&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;delegate&lt;/code&gt; edges authored by &lt;code&gt;U&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;private &lt;code&gt;exclude&lt;/code&gt; edges authored by &lt;code&gt;U&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;public &lt;code&gt;delegate&lt;/code&gt; edges reachable through delegation.&lt;/li&gt;
&lt;li&gt;public decisions reachable through delegation.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Private edges authored by other actors are never used. Exclude edges are never imported.&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;scheme&quot;&gt;(define (delegated-decisions U R C)
  (append-map
   (lambda (edge)
     (let ((T (edge-target edge)))
       (if (excluded? U R C T)
           '()
           (traverse U R C T (edge-hops edge) '()))))
   (delegate-edges-authored-by U R C)))
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code infostring=&quot;scheme&quot;&gt;(define (traverse U R C X hops visited)
  (cond
   ((member X visited) '())
   ((excluded? U R C X) '())
   (else
    (append
     (public-importable-decisions X R C)
     (if (zero? hops)
         '()
         (append-map
          (lambda (edge)
            (traverse U R C
                      (edge-target edge)
                      (hops-min (hops-dec hops) (edge-hops edge))
                      (cons X visited)))
          (public-delegate-edges X R C)))))))
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code infostring=&quot;scheme&quot;&gt;(define (hops-dec hops)
  (if (eq? hops 'unbounded)
      'unbounded
      (- hops 1)))

(define (hops-min a b)
  (cond
   ((eq? a 'unbounded) b)
   ((eq? b 'unbounded) a)
   (else (min a b))))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Helper meanings:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;delegate-edges-authored-by&lt;/code&gt; returns public or private delegate
edges authored by &lt;code&gt;U&lt;/code&gt; for capability &lt;code&gt;C&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;excluded?&lt;/code&gt; checks whether identity &lt;code&gt;X&lt;/code&gt; is privately excluded from
delegation by &lt;code&gt;U&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;public-importable-decisions&lt;/code&gt; returns public decisions that
capability &lt;code&gt;C&lt;/code&gt; allows to be imported.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;public-delegate-edges&lt;/code&gt; returns public delegate edges.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;edge-hops&lt;/code&gt; is a non-negative integer or &lt;code&gt;unbounded&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Note that in the above &lt;code&gt;traverse&lt;/code&gt; function, the &lt;code&gt;visited&lt;/code&gt; argument only
detects cycles within the current recursive branch. Implementations
SHOULD deduplicate returned decisions, for example by stable PubSub
item ID. Implementations MAY use caching or a global visited structure
only if doing so preserves the semantics of &lt;code&gt;hops&lt;/code&gt; and local
exclusions.&lt;/p&gt;
&lt;p&gt;Implementations MUST detect cycles and SHOULD impose limits on
recursion depth, edge count, node count, and computation time.&lt;/p&gt;
&lt;h1&gt;Mute Capability&lt;/h1&gt;
&lt;h2&gt;17. Capability Name&lt;/h2&gt;
&lt;p&gt;The mute capability name is:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;text&quot;&gt;mute
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The mute capability controls local rendering of ordinary MUC messages.&lt;/p&gt;
&lt;p&gt;It does not affect room state and does not grant moderation power.&lt;/p&gt;
&lt;h2&gt;18. Capability namespace&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;mute&lt;/code&gt; capability uses only the generic edge payload format
defined by this specification. It does not define a separate
capability-specific XML namespace.&lt;/p&gt;
&lt;h2&gt;19. Mute Decision Actions&lt;/h2&gt;
&lt;p&gt;The mute capability defines two decision actions:&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Action&lt;/th&gt;
      &lt;th&gt;Meaning&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code&gt;suppress&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Locally suppress matching messages&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code&gt;allow&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Locally override delegated suppress decisions&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;19.1 Suppress&lt;/h3&gt;
&lt;p&gt;A &lt;code&gt;suppress&lt;/code&gt; decision means the actor does not want matching ordinary
MUC messages shown in the main conversation view.&lt;/p&gt;
&lt;p&gt;A client SHOULD suppress notifications for suppressed messages.&lt;/p&gt;
&lt;p&gt;A client MAY provide a way to reveal suppressed messages.&lt;/p&gt;
&lt;h3&gt;19.2 Allow&lt;/h3&gt;
&lt;p&gt;An &lt;code&gt;allow&lt;/code&gt; decision is a private local override. It causes matching
messages to remain visible to the local actor even if they are
suppressed by delegated decisions or by the actor’s own active
&lt;code&gt;suppress&lt;/code&gt; decisions.&lt;/p&gt;
&lt;p&gt;An &lt;code&gt;allow&lt;/code&gt; decision does not retract, modify, or hide any public
&lt;code&gt;suppress&lt;/code&gt; decision from other users. Users who delegate to the actor
may still import the actor’s public &lt;code&gt;suppress&lt;/code&gt; decisions.&lt;/p&gt;
&lt;p&gt;Clients SHOULD warn when a local &lt;code&gt;allow&lt;/code&gt; conflicts with an active
public or private &lt;code&gt;suppress&lt;/code&gt; authored by the same actor.&lt;/p&gt;
&lt;h2&gt;20. Mute Edge Visibility Rules&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;suppress&lt;/code&gt; decisions may be public or private.  Public &lt;code&gt;suppress&lt;/code&gt;
decisions may be imported through delegations.&lt;/p&gt;
&lt;p&gt;For conceptual and computational simplicity, only &lt;code&gt;suppress&lt;/code&gt; decisions
can be imported from delegates.  Therefore, &lt;code&gt;allow&lt;/code&gt; edges are always
local, and MUST NOT be imported through delegation.&lt;/p&gt;
&lt;h2&gt;21. Mute Targets&lt;/h2&gt;
&lt;p&gt;Mute decision edges may target:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;identities,&lt;/li&gt;
&lt;li&gt;messages,&lt;/li&gt;
&lt;li&gt;threads.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Mute &lt;code&gt;delegate&lt;/code&gt; and &lt;code&gt;exclude&lt;/code&gt; edges target identities only.&lt;/p&gt;
&lt;h3&gt;21.1 Identity Target&lt;/h3&gt;
&lt;p&gt;An identity target uses &lt;code&gt;jid&lt;/code&gt; or &lt;code&gt;occupant-id&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;xml&quot;&gt;&amp;lt;target jid=&amp;quot;bob@example.com&amp;quot;/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;xml&quot;&gt;&amp;lt;target occupant-id=&amp;quot;b2&amp;quot;/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;21.2 Message Target&lt;/h3&gt;
&lt;p&gt;A message target uses a XEP-0359 stanza ID.&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;xml&quot;&gt;&amp;lt;target stanza-id=&amp;quot;msg-123&amp;quot; by=&amp;quot;room@example.org&amp;quot;/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;stanza-id&lt;/code&gt; attribute contains the XEP-0359 stable stanza ID. The
&lt;code&gt;by&lt;/code&gt; attribute identifies the assigning entity.&lt;/p&gt;
&lt;h3&gt;21.3 Thread Target&lt;/h3&gt;
&lt;p&gt;Mute decision edges MAY target RFC 6121 message thread
identifiers. Clients that do not support or preserve &lt;code&gt;&amp;lt;thread/&amp;gt;&lt;/code&gt; MAY
ignore thread targets.&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;xml&quot;&gt;&amp;lt;target thread=&amp;quot;thread-123&amp;quot;/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Thread identifiers are room-scoped by the edge’s &lt;code&gt;room&lt;/code&gt; attribute.&lt;/p&gt;
&lt;h2&gt;22. Effective Mute Evaluation&lt;/h2&gt;
&lt;p&gt;For local actor &lt;code&gt;U&lt;/code&gt; in room &lt;code&gt;R&lt;/code&gt;, effective mute targets are:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;scheme&quot;&gt;(define (effective-mute U R)
  (difference
   (union
    (direct-suppress U R)
    (delegated-suppress U R))
   (local-allow U R)))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Where:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;direct-suppress&lt;/code&gt; returns &lt;code&gt;suppress&lt;/code&gt; decisions authored by &lt;code&gt;U&lt;/code&gt;,
whether public or private.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;delegated-suppress&lt;/code&gt; returns imported public &lt;code&gt;suppress&lt;/code&gt; decisions.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;local-allow&lt;/code&gt; returns private &lt;code&gt;allow&lt;/code&gt; decisions authored by &lt;code&gt;U&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A local &lt;code&gt;allow&lt;/code&gt; decision cancels matching &lt;code&gt;suppress&lt;/code&gt; decisions for the
local actor’s own view, including &lt;code&gt;suppress&lt;/code&gt; decisions authored by the
local actor.&lt;/p&gt;
&lt;p&gt;A local &lt;code&gt;allow&lt;/code&gt; decision does not retract or modify any public
&lt;code&gt;suppress&lt;/code&gt; decision. Therefore, if Alice publicly suppresses Bob and
privately allows Bob, Alice’s client shows Bob, but users who delegate
mute decisions to Alice can still import Alice’s public suppress
decision for Bob.&lt;/p&gt;
&lt;p&gt;Clients SHOULD warn when a local &lt;code&gt;allow&lt;/code&gt; conflicts with an active
public or private &lt;code&gt;suppress&lt;/code&gt; authored by the same actor.&lt;/p&gt;
&lt;h2&gt;23. Mute Message Filtering&lt;/h2&gt;
&lt;p&gt;When rendering a MUC message, the client determines whether the
message matches any target in &lt;code&gt;effective-mute&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;A message is suppressed if any of the following match:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;sender identity,&lt;/li&gt;
&lt;li&gt;message stanza ID,&lt;/li&gt;
&lt;li&gt;thread identifier.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;24. Mute Examples&lt;/h2&gt;
&lt;p&gt;The following examples show &lt;code&gt;&amp;lt;edge/&amp;gt;&lt;/code&gt; payloads only. Public examples
are published as PubSub &lt;code&gt;&amp;lt;item/&amp;gt;&lt;/code&gt; payloads in the room public graph
node.  Private examples are stored in private PEP or local storage.&lt;/p&gt;
&lt;h3&gt;24.1 Direct Public Mute&lt;/h3&gt;
&lt;p&gt;Carol mutes Dave publicly in a non-anonymous room:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;xml&quot;&gt;&amp;lt;edge xmlns=&amp;quot;urn:xmpp:subjective-delegation:0&amp;quot;
      type=&amp;quot;decision&amp;quot;
      capability=&amp;quot;mute&amp;quot;
      action=&amp;quot;suppress&amp;quot;
      room=&amp;quot;room@example.org&amp;quot;&amp;gt;
  &amp;lt;actor jid=&amp;quot;carol@example.com&amp;quot;/&amp;gt;
  &amp;lt;target jid=&amp;quot;dave@example.com&amp;quot;/&amp;gt;
&amp;lt;/edge&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;24.2 Local Allow&lt;/h3&gt;
&lt;p&gt;Alice allows Dave, overriding any potential &lt;code&gt;mute&lt;/code&gt; decisions:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;xml&quot;&gt;&amp;lt;edge xmlns=&amp;quot;urn:xmpp:subjective-delegation:0&amp;quot;
      type=&amp;quot;decision&amp;quot;
      capability=&amp;quot;mute&amp;quot;
      action=&amp;quot;allow&amp;quot;
      room=&amp;quot;room@example.org&amp;quot;&amp;gt;
  &amp;lt;actor jid=&amp;quot;alice@example.com&amp;quot;/&amp;gt;
  &amp;lt;target jid=&amp;quot;dave@example.com&amp;quot;/&amp;gt;
&amp;lt;/edge&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Dave is visible to Alice.  If Alice also has an active public
&lt;code&gt;suppress&lt;/code&gt; decision for Dave, users who delegate mute decisions to
Alice may still suppress Dave.&lt;/p&gt;
&lt;h3&gt;24.3 Message-Specific Mute&lt;/h3&gt;
&lt;p&gt;Alice suppresses a particular message:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;xml&quot;&gt;&amp;lt;edge xmlns=&amp;quot;urn:xmpp:subjective-delegation:0&amp;quot;
      type=&amp;quot;decision&amp;quot;
      capability=&amp;quot;mute&amp;quot;
      action=&amp;quot;suppress&amp;quot;
      room=&amp;quot;room@example.org&amp;quot;&amp;gt;
  &amp;lt;actor jid=&amp;quot;alice@example.com&amp;quot;/&amp;gt;
  &amp;lt;target stanza-id=&amp;quot;msg-123&amp;quot; by=&amp;quot;room@example.org&amp;quot;/&amp;gt;
&amp;lt;/edge&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In message targets, the &lt;code&gt;by&lt;/code&gt; attribute MUST be present and MUST match
the &lt;code&gt;by&lt;/code&gt; attribute of the XEP-0359 &lt;code&gt;&amp;lt;stanza-id/&amp;gt;&lt;/code&gt; being referenced. In
typical MUC usage, this value is the room JID and therefore matches
the edge’s &lt;code&gt;room&lt;/code&gt; attribute.&lt;/p&gt;
&lt;h3&gt;24.4 Thread Mute&lt;/h3&gt;
&lt;p&gt;Alice suppresses a thread:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;xml&quot;&gt;&amp;lt;edge xmlns=&amp;quot;urn:xmpp:subjective-delegation:0&amp;quot;
      type=&amp;quot;decision&amp;quot;
      capability=&amp;quot;mute&amp;quot;
      action=&amp;quot;suppress&amp;quot;
      room=&amp;quot;room@example.org&amp;quot;&amp;gt;
  &amp;lt;actor jid=&amp;quot;alice@example.com&amp;quot;/&amp;gt;
  &amp;lt;target thread=&amp;quot;thread-123&amp;quot;/&amp;gt;
&amp;lt;/edge&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;25. Actor Authenticity&lt;/h2&gt;
&lt;p&gt;For bare-JID edges, the PubSub service MUST ensure that the publisher
is authorized to publish edges for the claimed actor JID.&lt;/p&gt;
&lt;p&gt;For occupant-ID edges, the PubSub service MUST ensure that the
publisher is authorized to publish edges for the claimed XEP-0421
occupant ID in the specified room.&lt;/p&gt;
&lt;p&gt;This may require integration between:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the MUC service,&lt;/li&gt;
&lt;li&gt;the PubSub service,&lt;/li&gt;
&lt;li&gt;the occupant-ID provider.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A client MUST NOT trust a self-asserted actor occupant ID in a public
PubSub item unless the service enforces this binding.&lt;/p&gt;
&lt;h2&gt;26. Access Control&lt;/h2&gt;
&lt;p&gt;Access control is required for both reading and writing.&lt;/p&gt;
&lt;p&gt;The service MUST prevent unauthorized publication of edges for another
actor.&lt;/p&gt;
&lt;p&gt;The service MUST prevent unauthorized retraction of another actor’s
edges.&lt;/p&gt;
&lt;p&gt;The room public graph node SHOULD use an access model consistent with
the room’s access policy.&lt;/p&gt;
&lt;p&gt;Private graph storage MUST be readable and writable only by the owning
user or authorized resources.&lt;/p&gt;
&lt;p&gt;In semi-anonymous and anonymous rooms, the PubSub service MUST NOT
expose bare JIDs through:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;payloads,&lt;/li&gt;
&lt;li&gt;node names,&lt;/li&gt;
&lt;li&gt;notifications,&lt;/li&gt;
&lt;li&gt;access-control side effects visible to unauthorized entities.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;27. Privacy Rules&lt;/h2&gt;
&lt;p&gt;In semi-anonymous and anonymous rooms, stable occupant-IDs are used to
identify actors and identity targets.  Bare JIDs MUST NOT appear.&lt;/p&gt;
&lt;p&gt;In non-anonymous rooms, bare JIDs are permitted because the room
already exposes them.&lt;/p&gt;
&lt;p&gt;Public delegation graphs may reveal social information. For example,
they may reveal who mutes whom and whose mute decisions are delegated
to by others.  Implementations SHOULD make this clear to users.&lt;/p&gt;
&lt;h2&gt;28. Interaction with Existing XEPs&lt;/h2&gt;
&lt;h3&gt;28.1 XEP-0045: Multi-User Chat&lt;/h3&gt;
&lt;p&gt;This protocol does not alter MUC roles, affiliations, bans, kicks,
voice, or moderation.  A delegate is not a MUC moderator.&lt;/p&gt;
&lt;h3&gt;28.2 XEP-0060: Publish-Subscribe&lt;/h3&gt;
&lt;p&gt;XEP-0060 is the storage and notification substrate for public room
graph state.&lt;/p&gt;
&lt;h3&gt;28.3 XEP-0163: Personal Eventing Protocol&lt;/h3&gt;
&lt;p&gt;PEP MAY be used for private multi-device synchronization.&lt;/p&gt;
&lt;p&gt;PEP MUST NOT be used as an authoritative source for public room graph
edges.&lt;/p&gt;
&lt;h3&gt;28.4 XEP-0359: Unique and Stable Stanza IDs&lt;/h3&gt;
&lt;p&gt;The mute capability may use XEP-0359 stanza IDs to target individual
messages.&lt;/p&gt;
&lt;h3&gt;28.5 XEP-0421: Occupant IDs&lt;/h3&gt;
&lt;p&gt;XEP-0421 is required for semi-anonymous and anonymous room support.&lt;/p&gt;
&lt;h3&gt;28.6 XEP-0313: Message Archive Management&lt;/h3&gt;
&lt;p&gt;Clients MAY apply the same capability-specific filtering to archived
messages.&lt;/p&gt;
&lt;h3&gt;28.7 XEP-0334: Message Processing Hints&lt;/h3&gt;
&lt;p&gt;Message Processing Hints are not required for protocol correctness.&lt;/p&gt;
&lt;p&gt;Services MAY include &lt;code&gt;no-store&lt;/code&gt; hints on PubSub notification messages.&lt;/p&gt;
&lt;h2&gt;29. Security Considerations&lt;/h2&gt;
&lt;p&gt;Implementations MUST consider:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;actor spoofing,&lt;/li&gt;
&lt;li&gt;occupant-ID forgery,&lt;/li&gt;
&lt;li&gt;unauthorized PubSub publication,&lt;/li&gt;
&lt;li&gt;unauthorized PubSub subscription,&lt;/li&gt;
&lt;li&gt;unauthorized item retraction,&lt;/li&gt;
&lt;li&gt;deanonymization through bare JID leakage,&lt;/li&gt;
&lt;li&gt;cycles in delegation graphs,&lt;/li&gt;
&lt;li&gt;denial of service through large graphs,&lt;/li&gt;
&lt;li&gt;stale or rotated occupant IDs.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;30. Client UX Recommendations&lt;/h2&gt;
&lt;p&gt;Clients SHOULD distinguish:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;direct local decisions,&lt;/li&gt;
&lt;li&gt;delegated decisions,&lt;/li&gt;
&lt;li&gt;local allow rules,&lt;/li&gt;
&lt;li&gt;local exclude rules,&lt;/li&gt;
&lt;li&gt;delegates with limited hops,&lt;/li&gt;
&lt;li&gt;delegates with recursive traversal.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For the mute capability, suggested UI actions include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;private mute,&lt;/li&gt;
&lt;li&gt;public mute,&lt;/li&gt;
&lt;li&gt;unmute&lt;/li&gt;
&lt;li&gt;allow despite delegated mute,&lt;/li&gt;
&lt;li&gt;remove allow rule,&lt;/li&gt;
&lt;li&gt;delegate mute decisions,&lt;/li&gt;
&lt;li&gt;set delegate hops,&lt;/li&gt;
&lt;li&gt;exclude delegate,&lt;/li&gt;
&lt;li&gt;remove exclude rule.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Instead of offering an option to set delegate hops, a simple
delegation UI may provide a choice between zero-hop &amp;quot;delegates&amp;quot; and
unbounded &amp;quot;superdelegates&amp;quot;.&lt;/p&gt;
&lt;p&gt;If a user selects “unmute” for a participant muted only through
delegation, the client SHOULD create a private &lt;code&gt;allow&lt;/code&gt; decision.&lt;/p&gt;
&lt;p&gt;If a user selects “unmute” for a participant directly muted by the
user, the client SHOULD retract the direct &lt;code&gt;suppress&lt;/code&gt; decision instead
of creating a private &lt;code&gt;allow&lt;/code&gt; decision.&lt;/p&gt;
&lt;p&gt;If a user excludes a directly appointed delegate, the client SHOULD
normally retract the direct &lt;code&gt;delegate&lt;/code&gt; edge instead of creating an
&lt;code&gt;exclude&lt;/code&gt; edge.&lt;/p&gt;
&lt;h3&gt;30.1 Delegation graph&lt;/h3&gt;
&lt;p&gt;Clients MAY provide a navigable interface for viewing the graph of
delegates for a particular room and capability.  See the &amp;quot;Prior Art&amp;quot;
section for examples.&lt;/p&gt;
&lt;h2&gt;31. Delegation Seeds&lt;/h2&gt;
&lt;p&gt;A future version of this specification may define delegation seeds for
bootstrapping a participant’s private delegation graph when joining a
room.&lt;/p&gt;
&lt;p&gt;A delegation seed is a recommended set of initial private edges
associated with a room. For example, an invitation link or
out-of-band invitation mechanism could recommend that a joining
participant delegate the &lt;code&gt;mute&lt;/code&gt; capability to one or more existing
room participants.&lt;/p&gt;
&lt;p&gt;A seed does not affect any participant unless that participant’s
client accepts it.  If accepted, seeded edges are created as private
edges authored by the joining participant.&lt;/p&gt;
&lt;p&gt;Seed child &lt;code&gt;&amp;lt;edge/&amp;gt;&lt;/code&gt; elements are templates, not graph edges. They
therefore omit the actor. The actor is filled in by the accepting
client.&lt;/p&gt;
&lt;p&gt;Example seed:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;xml&quot;&gt;&amp;lt;seed xmlns=&amp;quot;urn:xmpp:subjective-delegation:0&amp;quot;
      room=&amp;quot;room@example.org&amp;quot;&amp;gt;
  &amp;lt;edge type=&amp;quot;delegate&amp;quot; capability=&amp;quot;mute&amp;quot; hops=&amp;quot;0&amp;quot;&amp;gt;
    &amp;lt;target jid=&amp;quot;carol@example.com&amp;quot;/&amp;gt;
  &amp;lt;/edge&amp;gt;
  &amp;lt;edge type=&amp;quot;delegate&amp;quot; capability=&amp;quot;mute&amp;quot; hops=&amp;quot;unbounded&amp;quot;&amp;gt;
    &amp;lt;target jid=&amp;quot;dana@example.com&amp;quot;/&amp;gt;
  &amp;lt;/edge&amp;gt;
&amp;lt;/seed&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If Alice accepts this seed, her client creates private edges like the
following:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;xml&quot;&gt;&amp;lt;edge xmlns=&amp;quot;urn:xmpp:subjective-delegation:0&amp;quot;
      type=&amp;quot;delegate&amp;quot;
      capability=&amp;quot;mute&amp;quot;
      hops=&amp;quot;0&amp;quot;
      room=&amp;quot;room@example.org&amp;quot;&amp;gt;
  &amp;lt;actor jid=&amp;quot;alice@example.com&amp;quot;/&amp;gt;
  &amp;lt;target jid=&amp;quot;carol@example.com&amp;quot;/&amp;gt;
&amp;lt;/edge&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Clients MAY also use the existing set of room owners, admins, and
moderators as seed delegates.&lt;/p&gt;
&lt;p&gt;Future work:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;how seeds are encoded in XMPP URIs,&lt;/li&gt;
&lt;li&gt;how seed data is fetched from PubSub or another source,&lt;/li&gt;
&lt;li&gt;whether seeds are signed,&lt;/li&gt;
&lt;li&gt;how clients verify seed authenticity,&lt;/li&gt;
&lt;li&gt;how invitation links or seed access tokens interact with XMPP access control,&lt;/li&gt;
&lt;li&gt;how seeds represent occupant-ID targets in anonymous or
semi-anonymous rooms.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;32. Open Issues&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Exact PubSub node naming convention.&lt;/li&gt;
&lt;li&gt;Whether room public graph nodes must be hosted by the MUC service.&lt;/li&gt;
&lt;li&gt;How edge IDs are generated, e.g., using deterministic hashes.&lt;/li&gt;
&lt;li&gt;Handling occupant-ID rotation policies.&lt;/li&gt;
&lt;li&gt;Cache expiration after users leave a room.&lt;/li&gt;
&lt;li&gt;Fine-grained muting, such as muting a participant within a
particular thread,&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;33. Prior Art and Related Work&lt;/h2&gt;
&lt;p&gt;Credit for inspiring this XEP goes to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://cblgh.org/trustnet/&quot;&gt;Alexander Cobleigh’s TrustNet&lt;/a&gt;, which
describes a subjective trust graph model in which users can delegate
moderation-like decisions to trusted peers, producing different
local views for different participants.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://cabal.chat/moderation.html&quot;&gt;The Cable moderation protocol&lt;/a&gt;,
used in Cabal, which explores moderation as graph-based,
user-centered social state rather than solely centralized server
authority.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://emacsconf.org/2024/talks/hyperdrive/&quot;&gt;USHIN’s hyperdrive.el EmacsConf 2024
presentation&lt;/a&gt;, which
demonstrates a navigable UI for viewing the graph of sources/delegates.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;34. Summary&lt;/h2&gt;
&lt;p&gt;This specification defines subjective delegation for MUC using PubSub.&lt;/p&gt;
&lt;p&gt;The generic model is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;decision&lt;/code&gt;: express a decision within a capability,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;delegate&lt;/code&gt;: import another identity’s public decisions for a
capability,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;exclude&lt;/code&gt;: locally remove an identity from delegated traversal,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;capability&lt;/code&gt;: a named decision area with capability-specific semantics.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This specification also defines the &lt;code&gt;mute&lt;/code&gt; capability:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;suppress&lt;/code&gt;: locally suppress matching messages,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;allow&lt;/code&gt;: locally override delegated suppress decisions.&lt;/li&gt;
&lt;/ul&gt;
</content></entry><entry><title>Moving to Dali Gratitude / 搬家感恩记</title><id>https://breatheoutbreathe.in/posts/moving-to-dali-gratitude-搬家感恩记.html</id><author><name>谭九 Joseph and 贝贝 Bei Bei</name><email>contact@breatheoutbreathe.in</email></author><updated>2026-08-24T12:00:00Z</updated><link href="https://breatheoutbreathe.in/posts/moving-to-dali-gratitude-搬家感恩记.html" rel="alternate" /><content type="html">&lt;p&gt;We are now in our new space on the outskirts of Jiangdu Village,
Xizhou Town, Dali, Yunnan!  Thank you so much to Mama Apple and
Stepdad Dai Bobo for their loving support as we transition into our
new home!&lt;/p&gt;
&lt;p&gt;我们住进了云南大理喜洲镇江渡村郊外的新家！特别感谢妈妈和戴伯伯在我们搬
家的这段过渡时期给予我们满满的爱与支持！&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/static/images/2026-08-24-moving-to-dali-gratitude/2026-08-10-bei-bei-joseph-apple-dai-bobo-1.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Mama Apple and Stepdad Dai Bobo helped us pack things up in Zhuzhou, Hunan, including a full-size electric drum kit.&lt;/p&gt;
&lt;p&gt;妈妈和戴伯伯帮助我们把湖南株洲的行李收拾打包，其中还包括一套全尺寸的电子鼓。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/static/images/2026-08-24-moving-to-dali-gratitude/2026-08-05-bei-bei-drum-kit.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;They spent several days helping me jump through hoops to get a Chines e drivers license, and then they did most of the driving from Hunan to Yunnan, over 1200 miles!&lt;/p&gt;
&lt;p&gt;他们花了好几天帮我费尽周折地办理中国驾照，之后又几乎全程开车载着我们一路从湖南到了云南，路程超过1200英里！&lt;/p&gt;
&lt;p&gt;In Dali, they helped us carefully pack up everything from our Sanwenbi home, where Bei Bei and I stayed last year. With the help of a moving van, we moved everything over to the new place, and they took care of food shopping, cooking, and cleaning. They helped us think about how to organize our new space. They encouraged us to be confident in our negotiations in the lease transfer. They gifted us their car!!&lt;/p&gt;
&lt;p&gt;在大理，他们又帮我们仔细打包好了三文笔村房子的行李——那是贝贝和我去年住的地方。我们用货拉拉把所有东西都搬到了新家后，他们还包揽了买菜、做饭和打扫卫生。他们帮我们一起想办法布置新家，并鼓励我们谈租约转让时大胆争取自己的权益。他们甚至把车送给了我们！！&lt;/p&gt;
&lt;p&gt;After several weeks of toil, when I expressed my thanks to Stepdad Dai Bobo, he simply said, &amp;quot;必须的 (It must be)&amp;quot;.&lt;/p&gt;
&lt;p&gt;在几个星期的忙碌之后，当我向戴伯伯表达感谢时，他只是简单地说了一句：“必须的。”&lt;/p&gt;
&lt;p&gt;Thank you, Mama Apple and Stepdad Dai Bobo!!!&lt;/p&gt;
&lt;p&gt;谢谢妈妈和戴伯伯！！！&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/static/images/2026-08-24-moving-to-dali-gratitude/2026-08-10-bei-bei-joseph-apple-dai-bobo-2.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;We wish you safe and happy travels on your adventure to the island of
Bali this week!&lt;/p&gt;
&lt;p&gt;祝您们这周的巴厘岛探险之旅安全和愉快!&lt;/p&gt;
</content></entry><entry><title>Black Mouth Berry / 黑嘴巴果</title><id>https://breatheoutbreathe.in/posts/black-mouth-berry-黑嘴巴果.html</id><author><name>贝贝 Bei Bei and 谭九 Joseph</name><email>contact@breatheoutbreathe.in</email></author><updated>2026-07-23T12:00:00Z</updated><link href="https://breatheoutbreathe.in/posts/black-mouth-berry-黑嘴巴果.html" rel="alternate" /><content type="html">&lt;p&gt;Today, Bei Bei and I went for a short hike up Phoenix Mountain in
Zhuzhou, Hunan Province and found a patch of low-growing berries,
&lt;a href=&quot;https://tropical.theferns.info/viewtropical.php?id=Melastoma+dodecandrum&quot;&gt;Melastoma
dodecandrum&lt;/a&gt;
(地棯).  It's edible, sweet with an astringent and slightly bitter
aftertaste.  The &lt;a href=&quot;https://tcmwiki.com/wiki/twelvestamen-melastoma-herb&quot;&gt;whole plant is used in traditional Chinese
medicine&lt;/a&gt;.
According to a &lt;a href=&quot;https://pubmed.ncbi.nlm.nih.gov/15139128/&quot;&gt;2003 article published in China Journal of Chinese
Materia Medica&lt;/a&gt;, &lt;q cite=&quot;https://pubmed.ncbi.nlm.nih.gov/15139128/&quot;&gt;Five compounds were
isolated and identified as hexacosanoic acid (I), quercetin(II),
avicularin(III), gallic acid (IV), kaempferol(V).&lt;/q&gt;&lt;/p&gt;
&lt;p&gt;今天，贝贝和我去湖南株洲的凤凰山爬山，并发现了一片贴地生长的小浆
果——&lt;a href=&quot;https://tropical.theferns.info/viewtropical.php?id=Melastoma+dodecandrum&quot;&gt;地
菍
&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;它的果实可以食用，味道甜甜的，之后会留下涩味和一点苦味。&lt;a href=&quot;https://tcmwiki.com/wiki/twelvestamen-melastoma-herb&quot;&gt;地菍全株可入
中药&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;根据《中国中药杂志》2003 年发表的&lt;a href=&quot;https://pubmed.ncbi.nlm.nih.gov/15139128/&quot;&gt;一篇文
章&lt;/a&gt;，研究人员从地菍中分离并
鉴定出了五种化合物： &lt;q cite=&quot;https://pubmed.ncbi.nlm.nih.gov/15139128/&quot;&gt;二十六烷酸（Ⅰ）、槲皮
素（Ⅱ）、扁蓄苷（Ⅲ）、没食子酸（Ⅳ）和山柰酚（Ⅴ）。&lt;/q&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/static/images/2026-07-23-black-mouth-berry/2026-07-23-melastoma-close-up.jpg&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;/static/images/2026-07-23-black-mouth-berry/2026-07-23-melastoma-hand.jpg&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;/static/images/2026-07-23-black-mouth-berry/2026-07-23-melastoma-patch.jpg&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;/static/images/2026-07-23-black-mouth-berry/2026-07-23-melastoma-pinch.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The Latin name literally means &amp;quot;black mouth&amp;quot;, since the fruit stains
your mouth.  Perhaps it could also be used as a dye.&lt;/p&gt;
&lt;p&gt;它的拉丁属名，字面意思是“黑嘴”，因为果实会把嘴染黑。也许它还可以用来做染料。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/static/images/2026-07-23-black-mouth-berry/2026-07-23-melastoma-tongue.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
</content></entry><entry><title>小過 (歌词 + 视频) / Xiao Guo (Lyrics + Video)</title><id>https://breatheoutbreathe.in/posts/小過-歌词-视频-xiao-guo-lyrics-video.html</id><author><name>贝贝 Bei Bei and 谭九 Joseph</name><email>contact@breatheoutbreathe.in</email></author><updated>2026-07-22T12:00:00Z</updated><link href="https://breatheoutbreathe.in/posts/小過-歌词-视频-xiao-guo-lyrics-video.html" rel="alternate" /><content type="html">&lt;p&gt;卦辞 The Judgment&lt;/p&gt;
&lt;p&gt;小過。亨，利貞。可小事，不可大事。飛鳥遺之音。不宜上，宜下，大吉。&lt;/p&gt;
&lt;p&gt;Preponderance of the Small.
Success.
Perseverance furthers.
Small things may be done; great things should not be done.
The flying bird brings the message:
It is not well to strive upward,
It is well to remain below.
Great good fortune.&lt;/p&gt;
&lt;p&gt;&lt;video controls=&quot;true&quot; poster=&quot;/static/images/xiao-guo-cover.jpg&quot; src=&quot;/light/2026-02-19--tucson-wedding/2026-02-20-bei-bei-joseph-xiao-guo.mp4&quot;&gt;
&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;歌词 Lyrics:&lt;/p&gt;
&lt;p&gt;小小飞鸟飞得低&lt;/p&gt;
&lt;p&gt;Little bird, flying low&lt;/p&gt;
&lt;p&gt;你家很远不用急&lt;/p&gt;
&lt;p&gt;Your home is far away, no need to hurry&lt;/p&gt;
&lt;p&gt;晒晒太阳太阳光&lt;/p&gt;
&lt;p&gt;The sunlight is shining down&lt;/p&gt;
&lt;p&gt;光会跟着你去每个地方&lt;/p&gt;
&lt;p&gt;Sunlight will follow you wherever you go&lt;/p&gt;
&lt;p&gt;云朵环绕山峰凝结成水滴&lt;/p&gt;
&lt;p&gt;Clouds around the mountaintop condensing into droplets&lt;/p&gt;
&lt;p&gt;奔涌的小溪归于湖底&lt;/p&gt;
&lt;p&gt;Streams rushing down, returning to the lake&lt;/p&gt;
&lt;p&gt;滋养生灵&lt;/p&gt;
&lt;p&gt;Nourishing life&lt;/p&gt;
&lt;p&gt;小鱼嬉戏&lt;/p&gt;
&lt;p&gt;Little fish are playing&lt;/p&gt;
&lt;p&gt;水草摇曳&lt;/p&gt;
&lt;p&gt;Reeds are swaying&lt;/p&gt;
&lt;p&gt;沙石被塑形&lt;/p&gt;
&lt;p&gt;Sand and stones are being shaped&lt;/p&gt;
&lt;p&gt;树木支撑土壤&lt;/p&gt;
&lt;p&gt;Trees holding the soil&lt;/p&gt;
&lt;p&gt;彩虹跨越天际&lt;/p&gt;
&lt;p&gt;Rainbows leaping across the horizon&lt;/p&gt;
&lt;p&gt;万物连接&lt;/p&gt;
&lt;p&gt;Everything is connected&lt;/p&gt;
&lt;p&gt;这叫生命&lt;/p&gt;
&lt;p&gt;This is life&lt;/p&gt;
&lt;p&gt;就像你&lt;/p&gt;
&lt;p&gt;And it's just like you&lt;/p&gt;
&lt;p&gt;小小飞鸟飞得低&lt;/p&gt;
&lt;p&gt;Little bird, flying low&lt;/p&gt;
&lt;p&gt;你家很远不用急&lt;/p&gt;
&lt;p&gt;Your home is far away, no need to hurry&lt;/p&gt;
&lt;p&gt;晒晒太阳太阳光&lt;/p&gt;
&lt;p&gt;The sunlight is shining down&lt;/p&gt;
&lt;p&gt;光会跟着你去每个地方&lt;/p&gt;
&lt;p&gt;Sunlight will follow you wherever you go&lt;/p&gt;
&lt;p&gt;万物连接&lt;/p&gt;
&lt;p&gt;Everything is connected&lt;/p&gt;
&lt;p&gt;这叫生命&lt;/p&gt;
&lt;p&gt;This is life&lt;/p&gt;
&lt;p&gt;就像你&lt;/p&gt;
&lt;p&gt;And it's just like you&lt;/p&gt;
</content></entry><entry><title>Justin Squires Wedding Speech / 婚礼致辞</title><id>https://breatheoutbreathe.in/posts/justin-squires-wedding-speech-婚礼致辞.html</id><author><name>Justin Squires</name><email>contact@breatheoutbreathe.in</email></author><updated>2026-07-21T12:00:00Z</updated><link href="https://breatheoutbreathe.in/posts/justin-squires-wedding-speech-婚礼致辞.html" rel="alternate" /><content type="html">&lt;p&gt;Joseph and Bei Bei,&lt;/p&gt;
&lt;p&gt;Your union is true. Your union is written. Your union is in faith and
true love. You guys are meant to be!&lt;/p&gt;
&lt;p&gt;Joseph, if there’s anything that I trust about you in the few years
I’ve known you, it’s that your path and your ways are guided by a
wisdom and intuition further reaching than yourself, and this guidance
shines clear and bright to me in every interaction I’ve had with you
since Archangeli’s phonology class back in 2017.&lt;/p&gt;
&lt;p&gt;You move with clear intention and a powerfully honest presence. Your
mundane, day to day actions are courageous in their orientation
towards a greater harmony of mind, body, and spirit for yourself,
those you love, and the whole world. All the many kinds of blessings
from heaven are with you, whether it be your music, your healing
hands, the ki that you move yourself and the world with, your
connection to the trees and the waters, your extraordinary heritage,
your mind which sings a song of discovery, deep and one-of-a-kind, and
your open and loving heart. Your substance is far more unique,
uplifting, and purposeful than any of us here will ever know, and in
all this I knew, before I met her, that your love Bei Bei sings a song
equal to you in beauty. Your way of life is far too honest, clear, and
in love for someone any less to have joined you in marriage as Bei Bei
joins you now. God has known the both of you, and in knowing you, he
knew your union. He knew your harmony, and he knew your completion. So
here we are!&lt;/p&gt;
&lt;p&gt;To see you two now, coming together in the most powerful connection
that humans can share, is reassurance to me that whatever is really
going on in this crazy world we live in is actually far more excellent
and inspiring than it may otherwise seem. When I see you two together,
I see that God’s design is deeper, truer, freer, further, greater.&lt;/p&gt;
&lt;p&gt;Bei Bei, Joseph is my brother across lifetimes, a special gift to me
from a higher place, and I see that you ground him, and you lift
him. You are clear water for him. You are his love. You center him,
and he holds you. I see how your tranquility, your levity, and your
honesty fortifies his heart. And to see you in this way for him is
inspiring to me. I love you, and I love that you love him in the
perfect and unique way that you do. You are his wife, and you are my
family. Welcome, and thank you for being here.&lt;/p&gt;
&lt;p&gt;Justin Squires&lt;/p&gt;
&lt;h2&gt;中文翻译：&lt;/h2&gt;
&lt;p&gt;Joseph 和贝贝：&lt;/p&gt;
&lt;p&gt;你们的结合是真实的，是早已写下的。你们因信仰与真爱而走到一起。你们本就
注定属于彼此！&lt;/p&gt;
&lt;p&gt;Joseph，在认识你的这些年里，如果说有什么是我始终深信不疑的，那就是：你
所走的道路、你行事的方式，一直受到一种超越你自身的智慧与直觉所引领。自
从 2017 年我们在 Archangeli 的音系学课上认识以来，在与你的每一次相处中，
我都能清晰而明亮地感受到这份引领。&lt;/p&gt;
&lt;p&gt;你带着明确的心意前行，也带着一种无比真诚而有力量的存在感。即使是在最平
凡的日常生活中，你所做的每一件事也都怀着勇气，并始终朝向一种更大的和
谐——让你自己、你所爱的人，乃至整个世界，在身体、心灵与精神之间更加完整。
来自上天的种种祝福都与你同在：你的音乐，你那双能够疗愈的手，你用来推动
自己、也影响这个世界的“气”，你与树木和水流之间的连接，你非凡的血脉与传
承，你那颗唱着探索之歌的心——深邃而独一无二——还有你敞开而充满爱的内心。
你真正的本质，比我们在场的任何人所能完全理解的都更加独特，更能带给人力
量，也更有方向和意义。正因为如此，在见到贝贝之前，我就已经知道，你所爱
的人，也一定会唱着一首与你同样美丽的歌。你的生活如此真诚、清澈而充满爱，
因此，今天与你步入婚姻的贝贝，也必定拥有与你相称的灵魂。上帝一直认识你
们两个人。而在认识你们的时候，祂也早已知道你们会结合。祂知道你们之间的
和谐，也知道你们会如何使彼此变得完整。于是，我们今天就在此庆祝！&lt;/p&gt;
&lt;p&gt;此刻，看着你们走到一起，进入人与人之间所能拥有的最深刻、最有力量的连接，
我感到一种深深的安慰。这让我相信，无论我们所生活的这个疯狂世界究竟正在
发生什么，它都远比表面看起来更加美好，也更加鼓舞人心。当我看着你们在一
起，我看见上帝的安排比我们所能想象的更深、更真、更自由、更辽阔，也更伟
大。&lt;/p&gt;
&lt;p&gt;贝贝，Joseph 是我生生世世的兄弟，是来自更高之处、赐予我的一份珍贵礼物。
我看见，你让他安稳地扎根，也让他向上生长。对他而言，你如清水一般澄澈。
你是他的爱。你让他的心安定下来，而他稳稳地承托着你。我看见你的宁静、你
的轻盈和你的真诚，如何让他的内心变得更加坚定。看见你以这样的方式陪伴着
他，也让我深受感动和鼓舞。&lt;/p&gt;
&lt;p&gt;我爱你，也爱你以只属于你的、完美而独特的方式爱着他。你是他的妻子，也是
我的家人。欢迎你，也谢谢你来到这里。&lt;/p&gt;
&lt;p&gt;Justin Squires&lt;/p&gt;
</content></entry><entry><title>外公的婚礼祝福 / Wedding Blessings from Grandpa</title><id>https://breatheoutbreathe.in/posts/外公的婚礼祝福-wedding-blessings-from-grandpa.html</id><author><name>贝贝 Bei Bei</name><email>contact@breatheoutbreathe.in</email></author><updated>2026-07-20T12:00:00Z</updated><link href="https://breatheoutbreathe.in/posts/外公的婚礼祝福-wedding-blessings-from-grandpa.html" rel="alternate" /><content type="html">&lt;p&gt;恭喜外孙女贝贝和小九新婚之喜！远在亚利桑那州缔结良缘，新春佳节双喜临门。外公特意填词两阕，既合中式雅韵，又寄万里祝福。两阕词皆紧扣新春、新婚、远地良缘之意，平仄韵脚依词牌正体格律，既合迎宾贺婚之礼，又藏阖家祝福之情。贝贝，如你们那里有“中国红的”大红纸买的话，你去买张红纸抄写一份贴在你们的新婚房间里，作个纪念。《迎宾词》用红纸抄写一份贴在你们办喜酒的大门。（外公外婆衷心祝福，2026年2月19日于中国）&lt;/p&gt;
&lt;p&gt;Congratulations to our granddaughter Bei Bei and Joseph on your marriage!&lt;/p&gt;
&lt;p&gt;You have joined your lives together far away in Arizona, and your wedding has brought us double happiness during the Chinese New Year. To celebrate, Grandpa has written two traditional Chinese poems especially for you. They carry the grace of Chinese poetry, as well as our blessings sent across thousands of miles.&lt;/p&gt;
&lt;p&gt;Both poems are about the Chinese New Year, your marriage, and the happy fate that brought you together in a faraway place. Their tones and rhymes follow the traditional forms. They are written both to welcome the guests and celebrate the wedding, while also carrying the love and blessings of the whole family.&lt;/p&gt;
&lt;p&gt;Bei Bei, if you can find some bright red paper—the traditional Chinese red—where you live, please buy a sheet and copy the poems onto it. You can hang them in your newlywed room as a keepsake.&lt;/p&gt;
&lt;p&gt;Please also copy the poem titled “Welcoming the Guests” onto red paper and hang it by the entrance of the place where you hold your wedding banquet.&lt;/p&gt;
&lt;p&gt;With all our hearts, Grandpa and Grandma wish you both a happy marriage and a beautiful life together.&lt;/p&gt;
&lt;p&gt;February 19, 2026
China&lt;/p&gt;
&lt;hr /&gt;&lt;p&gt;&lt;strong&gt;《春光好》&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Spring in Gentle Light&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;莺语软，
Soft birds sing,&lt;/p&gt;
&lt;p&gt;柳丝柔，
Willows sway softly,&lt;/p&gt;
&lt;p&gt;瑞烟浮。
Auspicious mists drift.&lt;/p&gt;
&lt;p&gt;万里天涯春正好，
Across ten thousand miles, spring is bright—&lt;/p&gt;
&lt;p&gt;缔鸾俦。
Lovebirds unite.&lt;/p&gt;
&lt;p&gt;锦瑟同心相绾，
Twin zither hearts bound as one,&lt;/p&gt;
&lt;p&gt;华堂笑靥凝眸。
Smiles meet in the bridal hall.&lt;/p&gt;
&lt;p&gt;岁岁琴瑟和韵永，
Year after year, harmonies run,&lt;/p&gt;
&lt;p&gt;福长留。
Blessings ever fall.&lt;/p&gt;
&lt;hr /&gt;&lt;p&gt;&lt;strong&gt;《浪淘沙·迎宾词》&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Welcoming Verse — Tune of Lang Tao Sha&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;嘉节启新章，
This fair day opens a chapter new,&lt;/p&gt;
&lt;p&gt;喜气盈堂。
Joy fills the hall through.&lt;/p&gt;
&lt;p&gt;宾朋同贺凤求凰。
Friends gather to bless the phoenix pair,&lt;/p&gt;
&lt;p&gt;万里姻缘天作合，
A bond heaven-made across miles so far—&lt;/p&gt;
&lt;p&gt;共祝安康。
All wish them peace to share.&lt;/p&gt;
&lt;p&gt;笑语暖壶觞，
Warm laughter lifts the wine,&lt;/p&gt;
&lt;p&gt;春意绵长。
Spring lingers, tender and long.&lt;/p&gt;
&lt;p&gt;兰闺佳偶岁月香。
A graceful couple, their years refined,&lt;/p&gt;
&lt;p&gt;白首齐眉情不改，
Side by side till hair turns white, love strong—&lt;/p&gt;
&lt;p&gt;世世荣昌。
Blessed through ages in song.&lt;/p&gt;
</content></entry><entry><title>我们的故事 (第一集) / Our Story (Episode 1)</title><id>https://breatheoutbreathe.in/posts/我们的故事-第一集-our-story-episode-1.html</id><author><name>贝贝 Bei Bei</name><email>contact@breatheoutbreathe.in</email></author><updated>2026-07-20T12:00:00Z</updated><link href="https://breatheoutbreathe.in/posts/我们的故事-第一集-our-story-episode-1.html" rel="alternate" /><content type="html">&lt;p&gt;2025年年初，我受航长的邀请参加每周一次的启发聚会（一系列帮助人了解基督教信仰的课程）。我那时住在下关，每次路上要花一个半小时，但我无比庆幸自己参与其中，因为在那里我认识了对我非常重要的一群好朋友。3月时我还信誓旦旦地告诉他们我懒得搬家，每周这么赶路也无所谓，转头4月1号我就已经住进了三文笔村的新家。我有了自己的沙盘工作室，也有了与朋友们更频繁的相聚。我们在聿敏家打鼓、跳舞，在航长家聚餐、欢唱，在Raymond家喝酒、畅聊……我的“闭关”状态渐渐褪去，被友情的连接所填充。6月的一天，我和姚瑶约好了吃午餐，她早上临时问我能不能再带一位刚认识的外国朋友一起，我随即就答应了，并不知道自己命运会因此发生翻天覆地的变化。在这个愉快的下午之后，我和谭九每天都会见面，每次见面聊天聊到不想告别。两周以后在一个聚会上我邀请谭九和我一起跳舞，我们两个十分笨拙地相拥跳舞，一不小心就踩到对方的脚。那天告别时我意识到，我要恋爱了。第二天谭九来到我家问我，虽然我们的未来充满了不确定，我是否愿意和他开始恋爱。我们的故事就这样在懵懂的心动中开始了。&lt;/p&gt;
&lt;p&gt;At the beginning of 2025, Hangzhang invited me to join a weekly Alpha gathering (a set of structured lessons for learning about Christianity). I was living in Xiaguan at the time, so getting there took an hour and a half each way. Even so, I am incredibly glad that I kept going, because that was where I met a group of friends who would become very important to me.&lt;/p&gt;
&lt;p&gt;As late as March, I was still confidently telling everyone that I was too lazy to move and perfectly happy to spend all that time on the road every week. And then, almost as soon as I had said it, I moved into my new home in Sanwenbi Village on April 1.&lt;/p&gt;
&lt;p&gt;The move gave me a sandplay studio of my own, but more importantly, it brought me much closer to my friends. We played drums and danced at Yumin’s place, gathered for food and singing at Hangzhang’s, and enjoyed homemade cocktails and conversation late into the night at Raymond’s. Without my really noticing, the “retreat” I had been living in slowly came to an end, and friendship began to fill the space it left behind.&lt;/p&gt;
&lt;p&gt;Then one day in June, I made plans to have lunch with Yaoyao. That morning, she asked at the last minute whether she could bring along a foreign friend she had just met. I agreed right away, with no idea that this small decision was about to transform my life.&lt;/p&gt;
&lt;p&gt;After that lovely afternoon, Joseph and I started seeing each other every day. Each time we met, we would talk until neither of us wanted to say goodbye.&lt;/p&gt;
&lt;p&gt;Two weeks later, at a gathering, I asked him to dance with me. We held each other and moved around very awkwardly, stepping on each other’s feet more than once. By the time we said goodbye that evening, I knew I was falling in love.&lt;/p&gt;
&lt;p&gt;The next day, Joseph came to my home and asked whether I would be willing to start a relationship with him, even though there was so much uncertainty ahead of us.&lt;/p&gt;
&lt;p&gt;That was how our story began: with a shy and slightly confused kind of excitement, before either of us fully understood where it would lead.&lt;/p&gt;
</content></entry><entry><title>Barrel Compost Toilet (Episode 1) / 桶式堆肥厕所 (第一集)</title><id>https://breatheoutbreathe.in/posts/barrel-compost-toilet-episode-1-桶式堆肥厕所-第一集.html</id><author><name>Joseph Turner</name><email>contact@breatheoutbreathe.in</email></author><updated>2026-07-17T12:00:00Z</updated><link href="https://breatheoutbreathe.in/posts/barrel-compost-toilet-episode-1-桶式堆肥厕所-第一集.html" rel="alternate" /><content type="html">&lt;p&gt;After &lt;a href=&quot;https://breatheoutbreathe.in/light/2026-02-19--tucson-wedding/index.html&quot;&gt;we got married in
February&lt;/a&gt;,
Bei Bei and I honeymooned at the &lt;a href=&quot;https://cascabelconservation.org/hermitage/&quot;&gt;Cascabel Conservation
Association&lt;/a&gt;'s Cottage
Hermitage, a sublime solitary retreat nestled in Hot Springs Canyon
east of Tucson, AZ.  We actually made plans &lt;em&gt;before&lt;/em&gt; we got married to
do the week-long retreat together in Cascabel!  Only after getting
engaged three weeks before our wedding did we realize that the retreat
would be the perfect honeymoon.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://breatheoutbreathe.in/light/2026-02-19--tucson-wedding/index.html&quot;&gt;二月结
婚
&lt;/a&gt;
后，贝贝和我来到亚利桑那州图森以东、热泉峡谷中的&lt;a href=&quot;https://cascabelconservation.org/hermitage/&quot;&gt;卡斯卡贝尔保护协
会&lt;/a&gt;隐修小屋度蜜月。那是一
处静谧绝美、远离尘嚣的独居隐居地。其实在结婚之前，我们就已经计划好，要
一起去卡斯卡贝尔进行为期一周的静修！直到婚礼前三周订婚后，我们才意识到，
这次静修正好可以成为一场再完美不过的蜜月。&lt;/p&gt;
&lt;p&gt;Immediately following our honeymoon-retreat week, we were blessed to
join in &lt;a href=&quot;https://saguarojuniper.org/&quot;&gt;Saguaro Juniper&lt;/a&gt;'s 3rd annual
&lt;a href=&quot;https://saguarojuniper.org/saguaro-juniper-home-on-the-range-march-6-8-cascabel-arizona/&quot;&gt;Home On the
Range&lt;/a&gt;
event.  We enjoyed a morning work-party, Papa Cobre patties, and
fulfilling conversation with old and new friends.&lt;/p&gt;
&lt;p&gt;蜜月静修结束后，我们又很幸运地参加了 &lt;a href=&quot;https://saguarojuniper.org/&quot;&gt;Saguaro
Juniper&lt;/a&gt; 举办的第三届年度“&lt;a href=&quot;https://saguarojuniper.org/saguaro-juniper-home-on-the-range-march-6-8-cascabel-arizona/&quot;&gt;Home On the
Range&lt;/a&gt;”
（牧场家园开放日）活动。那天上午，我们一起参加了集体劳动，品尝了 Papa
Cobre 肉饼，还和新老朋友们进行了一番充实而愉快的交流。&lt;/p&gt;
&lt;p&gt;David Omick and Pearl Mast, whose &lt;a href=&quot;https://www.omick.net/&quot;&gt;creatively minimal
lifestyle&lt;/a&gt; inspires us greatly, patiently
fielded countless questions about their brilliant barrel composting
design.  David, who only a few days prior underwent knee surgery,
hopped up to eagerly show us some of the composting toilets they built
and installed for private and community use around Cascabel.&lt;/p&gt;
&lt;p&gt;David Omick 和 Pearl Mast 极具创意的&lt;a href=&quot;https://www.omick.net/&quot;&gt;极简生活方
式&lt;/a&gt;一直深深启发着我们。他们耐心回答了我们关于
其巧妙桶式堆肥系统设计的无数问题。David 几天前才刚做完膝盖手术，却还是
一跃而起，兴致勃勃地带我们参观了他们在卡斯卡贝尔各处建造和安装的一些堆
肥厕所，这些设施既供私人家庭使用，也供社区共同使用。&lt;/p&gt;
&lt;p&gt;The 55-gallon drum composting toilet is safe (even &lt;a href=&quot;https://watershedmg.org/programs/composting-toilets&quot;&gt;approved by the
ADEQ&lt;/a&gt;), easy to
put together, and comfortable to use.  What's more, David and Pearl's designs
are available for anyone to study &lt;a href=&quot;https://www.omick.net/composting_toilets/barrel_toilet.htm&quot;&gt;for free on their
website&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;这种采用 55 加仑桶的堆肥厕所安全可靠——甚至获&lt;a href=&quot;https://watershedmg.org/programs/composting-toilets&quot;&gt;得了亚利桑那州环境质量部
（ADEQ）的批准&lt;/a&gt;——
组装简单，使用起来也很舒适。更棒的是，David 和 Pearl 已经把设计方案&lt;a href=&quot;https://www.omick.net/composting_toilets/barrel_toilet.htm&quot;&gt;免
费发布在他们的网站
上&lt;/a&gt;，任何
人都可以查看和学习！&lt;/p&gt;
&lt;p&gt;This blog post summarizes some thoughts I had while building a
composting toilet based on these designs.  Thank you, David and Pearl,
for sharing your wisdom!  Thank you, Dad, for carpentry guidance!
Thank you, Bei Bei, for diligent quality assurance testing!&lt;/p&gt;
&lt;p&gt;这篇博客文章总结了我依据这些设计建造堆肥厕所时产生的一些想法。
谢谢 David 和 Pearl，感谢你们无私分享自己的智慧！
谢谢爸爸，在木工方面给予我的指导！
谢谢贝贝，认真负责地完成了质量保证测试！&lt;/p&gt;
&lt;h2&gt;The Throne&lt;/h2&gt;
&lt;h2&gt;宝座&lt;/h2&gt;
&lt;p&gt;David and Pearl's original design recommends burying the barrels
halfway so that the business-doer can comfortably sit without having
to climb up stairs.  However we wanted to install our toilet on the
front porch, which already has a wood floor.  This meant that we
needed to build some steps to get up to the toilet:&lt;/p&gt;
&lt;p&gt;David 和 Pearl 的原始设计建议把桶体埋入地下约一半，这样如厕的人无需爬
楼梯，就能舒适地坐上去。不过，我们想把厕所安装在前廊上，而那里本来就铺
有木地板。这意味着，我们需要搭几级台阶才能坐上马桶：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/static/images/2026-07-17-barrel-compost-toilet-episode-one/steps-to-the-throne.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The main disadvantage to this approach is that it requires moving the
steps and the barrel after it's full, but with two people and a large
hand truck, it's no problem to replace the full barrel every few
months.  Alternatively, David's website suggests placing a plant dolly
underneath the barrel for easy transportation indoors.&lt;/p&gt;
&lt;p&gt;这种做法最大的缺点是，桶装满后，必须先移开台阶，再把桶搬走。不过，只要
有两个人，再配上一辆大型手推车，每隔几个月更换一次满桶并不是什么难事。
另一种办法是，按照 David 网站上的建议，在桶底下放一个带轮子的花盆托架，
这样在室内搬运起来会方便得多。&lt;/p&gt;
&lt;h2&gt;Toilet top&lt;/h2&gt;
&lt;h2&gt;马桶顶板&lt;/h2&gt;
&lt;p&gt;Instead of making the toilet top out of epoxy-coated wood, aluminum,
or concrete, we found a piece of 5/16&amp;quot; grey plastic material at the
scrapyard and cut out the toilet top and insect plate with a jigsaw.
It resists weathering and can be cleaned easily.&lt;/p&gt;
&lt;p&gt;我们没有用涂有环氧树脂的木板、铝板或混凝土来制作马桶顶板，而是在废品回
收场找到了一块厚约 8 毫米（5/16 英寸）的灰色塑料板材，再用曲线锯切割出
马桶顶板和防虫板。这种材料经得起日晒雨淋，也很容易清洁。&lt;/p&gt;
&lt;p&gt;We did not add a hold-down spring assembly, but instead relied on the
snug fit between the locator bolts (we used 3/8&amp;quot; bolts, less prone to
bending than the 1/4&amp;quot; bolts in the original design) and the holes
drilled around the lip of the barrel.&lt;/p&gt;
&lt;p&gt;我们没有安装弹簧压紧装置，而是依靠定位螺栓与桶口边缘所钻孔洞之间的紧密
配合来固定顶板。我们使用了直径约 10 毫米（3/8 英寸）的螺栓，比原设计中
直径约 6 毫米（1/4 英寸）的螺栓更不容易弯曲。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/static/images/2026-07-17-barrel-compost-toilet-episode-one/heavier-gauge-locator-bolts.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Urine diverter&lt;/h2&gt;
&lt;h2&gt;尿液分流器&lt;/h2&gt;
&lt;p&gt;David and Pearl's design calls for a funnel + rubber stopper as a
urine diverter.  I hacked together an alternative design using old
plastic jugs.  Cut a milk/vinegar/oil jug to the desired shape.  Drill
a hole in the lid of the jug the same diameter as your tubing.  Cut
the last 1/2&amp;quot; of the tubing open lengthwise, push it through the hole
in the lid, and screw the two flaps of the tubing down to the lid.
It's not perfectly watertight, but a little moisture is good for the
compost.  If your milk jug has a handle, cut the handle long, bend it
over, and screw it down to itself.&lt;/p&gt;
&lt;p&gt;David 和 Pearl 的设计使用漏斗和橡胶塞来制作尿液分流器。我则用几个废旧
塑料壶，临时拼出了另一种做法。先把牛奶壶、醋壶或食用油壶剪成需要的形状。
然后在壶盖上钻一个孔，孔径与导管外径相同。把导管末端约 1.3 厘米（1/2
英寸）长的一段纵向剪开，将导管穿过壶盖上的孔，再把剪开的两片分别用螺丝
固定在壶盖上。这个连接处并不能做到完全不漏水，不过堆肥中保留少量水分反
而是好事。如果牛奶壶带有把手，剪的时候把把手留长一些，再将它弯折过去，
用螺丝固定在把手自身上。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/static/images/2026-07-17-barrel-compost-toilet-episode-one/urine-diverter-side-disassembled-3.jpg&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;/static/images/2026-07-17-barrel-compost-toilet-episode-one/urine-diverter-side-disassembled-2.jpg&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;/static/images/2026-07-17-barrel-compost-toilet-episode-one/urine-diverter-side-disassembled-1.jpg&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;/static/images/2026-07-17-barrel-compost-toilet-episode-one/urine-diverter-side-view.jpg&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;/static/images/2026-07-17-barrel-compost-toilet-episode-one/urine-diverter-top-view.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Keeping the urine diverter clean&lt;/h2&gt;
&lt;h2&gt;保持尿液分流器清洁&lt;/h2&gt;
&lt;p&gt;Instead of using a stainless steel sink drain screen to keep the
diverter from clogging with misplaced cover material, I wrapped the
entire urine diverter with insect screen.  This way, when cover
material accidentally falls onto the diverter, it mostly bounces off,
reducing the need for manual cleaning.  However, the screen and urine
diverter can still be removed and cleaned, since it's fastened in
place by a piece of wire which can hinge up and out of the way.&lt;/p&gt;
&lt;p&gt;原设计使用不锈钢水槽滤网，防止误落的覆盖材料堵住尿液分流器。我没有采用
这种做法，而是用防虫网把整个尿液分流器包了起来。这样一来，覆盖材料即使
不小心掉到分流器上，大多也会弹开，从而减少手动清理的次数。不过，防虫网
和尿液分流器仍然可以拆下来清洗。它们由一段金属丝固定在原位，需要清理时，
可以把金属丝像铰链一样向上掀开，让到一旁。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/static/images/2026-07-17-barrel-compost-toilet-episode-one/urine-diverter-screen.jpg&quot; alt=&quot;&quot; /&gt;
&lt;video src=&quot;/static/images/2026-07-17-barrel-compost-toilet-episode-one/hinged-screen-holder.webm&quot; autoplay=&quot;true&quot; controls=&quot;true&quot; muted=&quot;true&quot; loop=&quot;true&quot; playsinline=&quot;true&quot;&gt;
&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;An alternative method for keep the urine diverter clean while the
barrel is aging is to turn the bottom of the milk jug into a cover
which fits snugly over the urine diverter.  That way, as you stir the
aging compost, no flying debris will fall in.&lt;/p&gt;
&lt;p&gt;在桶内的堆肥静置腐熟期间，还有另一种保持尿液分流器清洁的方法：把牛奶壶
的底部剪下来，做成一个能够严密扣在分流器上的盖子。这样，翻动正在腐熟的
堆肥时，飞散的碎屑就不会落进分流器里。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/static/images/2026-07-17-barrel-compost-toilet-episode-one/urine-diverter-1.jpg&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;/static/images/2026-07-17-barrel-compost-toilet-episode-one/urine-diverter-cover.jpg&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;/static/images/2026-07-17-barrel-compost-toilet-episode-one/urine-diverter-2.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Urine collection&lt;/h2&gt;
&lt;h2&gt;尿液收集&lt;/h2&gt;
&lt;p&gt;Instead of installing an underground leaching chamber, we opted to
save the urine for our garden!  Just run a line (we used rabbit
watering system hose) from the barrel through a hole in the wall and
into the lid of a 5-gallon bucket.  We also added orange peels into
the bucket to cut the smell.&lt;/p&gt;
&lt;p&gt;我们没有安装地下渗滤装置，而是决定把尿液收集起来，用在花园里！只需从桶
里接出一根软管——我们用的是兔用自动饮水系统的软管——让它穿过墙上的孔，再
接入一个约 19 升（5 加仑）塑料桶的桶盖即可。我们还在桶里放了一些橙皮，
以减轻异味。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/static/images/2026-07-17-barrel-compost-toilet-episode-one/rabbit-water-hose-urine-diverter.jpg&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;/static/images/2026-07-17-barrel-compost-toilet-episode-one/urine-collection-bucket.jpg&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;/static/images/2026-07-17-barrel-compost-toilet-episode-one/urine-collection-orange-peels.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;More fun hacks&lt;/h2&gt;
&lt;h2&gt;更多有趣的小改造&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;An old dish rack cut down and repurposed as a hanger for the insect plate.
Nice one, Dad!&lt;/li&gt;
&lt;li&gt;把一个旧碗碟沥水架裁短后重新利用，做成悬挂防虫板的架子。（好主意呀，
老爸！）&lt;/li&gt;
&lt;li&gt;Another way to install the mason jar insect trap is to directly
clamp a Fernco fitting onto the mason jar (the &lt;a href=&quot;https://www.omick.net/composting_toilets/CIMG7113,%20wil%20and%20mai%27s%20barrel%20toilet,%20small.jpg&quot;&gt;original
design&lt;/a&gt;
involves resting the mason jar in a sanitary tee, which I was
concerned would get knocked over if anyone went wild on the John)&lt;/li&gt;
&lt;li&gt;安装梅森玻璃罐捕虫器还有另一种方法：直接用 Fernco 橡胶接头把玻璃罐夹
紧固定。&lt;a href=&quot;https://www.omick.net/composting_toilets/CIMG7113,%20wil%20and%20mai%27s%20barrel%20toilet,%20small.jpg&quot;&gt;原设
计
&lt;/a&gt;
是把玻璃罐放在卫生三通接头里，但我担心万一有人上厕所时动作太大，会把
它碰倒。&lt;/li&gt;
&lt;li&gt;A plastic jug scoop for sawdust cover material.&lt;/li&gt;
&lt;li&gt;还可以用旧塑料壶做一个小铲子，用来舀作为覆盖材料的锯末。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/static/images/2026-07-17-barrel-compost-toilet-episode-one/dish-rack-hanger-for-toilet-lid.jpg&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;/static/images/2026-07-17-barrel-compost-toilet-episode-one/insect-trap.jpg&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;/static/images/2026-07-17-barrel-compost-toilet-episode-one/plastic-jug-scoop.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;Safe, Happy Compost&lt;/h1&gt;
&lt;h2&gt;安全又有活力的堆肥&lt;/h2&gt;
&lt;p&gt;After we filled up the first barrel, happy little sprouts began
popping up on top of the compost material, only to be turned under by
our compost crank each week.  The internal temperature of the compost
rose to 122℉ (50℃) and stayed there for a few days, high enough to
kill all potential parasites and pathogens, according to the &amp;quot;Time and
Temperature Safety Zone&amp;quot; graph from Joseph Jenkins's &lt;em&gt;Humanure
Handbook&lt;/em&gt; (Version 4), Chapter 13, page 204.&lt;/p&gt;
&lt;p&gt;第一个桶装满后，堆肥表面开始冒出一株株生机勃勃的小嫩芽，不过每周都会被
我们的堆肥搅拌器重新翻进堆料里。堆肥内部的温度升至 50℃（122℉），并维持
了数日。根据 Joseph Jenkins 所著《人粪堆肥手册》第四版第 13 章第 204
页中的“时间与温度安全区”图表，这一温度已经足以杀灭所有可能存在的寄生虫
和病原体。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/static/images/2026-07-17-barrel-compost-toilet-episode-one/happy-compost-toilet-sprout.jpg&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;/static/images/2026-07-17-barrel-compost-toilet-episode-one/compost-toilet-thermometer.jpg&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;/static/images/2026-07-17-barrel-compost-toilet-episode-one/time-and-temperature-safety-zone-joseph-jenkins-humanure-handbook.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;On the next episode of &lt;em&gt;Barrel Compost Toilet&lt;/em&gt;...&lt;/h1&gt;
&lt;h2&gt;《桶式堆肥厕所》下回预告……&lt;/h2&gt;
&lt;p&gt;Bei Bei and I are now preparing to move into our new home in Xizhou
Town, Dali City, Yunnan Province, China!  Stay tuned for more compost
toilet updates!&lt;/p&gt;
&lt;p&gt;贝贝和我正在准备搬进位于中国云南省大理市喜洲镇的新家！敬请期待更多关于
堆肥厕所的后续更新！&lt;/p&gt;
&lt;p&gt;We are tremendously grateful to all of the folks in Cascabel who
welcomed us so warmly!  We can't wait until the next time we get
together.  Thank you!!&lt;/p&gt;
&lt;p&gt;我们由衷感谢卡斯卡贝尔的每一位朋友，感谢大家如此热情地欢迎和接纳我们！
我们已经迫不及待地盼着下一次相聚了。谢谢大家！！&lt;/p&gt;
</content></entry><entry><title>Follow Up on emails with mu4e and org-capture</title><id>https://breatheoutbreathe.in/posts/follow-up-on-emails-with-mu4e-and-org-capture.html</id><author><name>Joseph Turner</name><email>contact@breatheoutbreathe.in</email></author><updated>2025-02-03T12:00:00Z</updated><link href="https://breatheoutbreathe.in/posts/follow-up-on-emails-with-mu4e-and-org-capture.html" rel="alternate" /><content type="html">&lt;p&gt;When I send an email, I often want my agenda to remind me to follow up
a few days later.  Thanks to &lt;a href=&quot;https://www.djcbsoftware.nl/&quot;&gt;Dirk-Jan C. Binnema&lt;/a&gt;'s help, I was able to
set this up with &lt;code&gt;mu4e&lt;/code&gt; and &lt;code&gt;org-capture&lt;/code&gt;.&lt;/p&gt;
&lt;h1&gt;&lt;code&gt;mu4e&lt;/code&gt; + &lt;code&gt;org-capture&lt;/code&gt;&lt;/h1&gt;
&lt;p&gt;Here's the code:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  (add-to-list
   'org-capture-templates
   (&amp;quot;efu&amp;quot; &amp;quot;Follow Up&amp;quot; entry (file+olp &amp;quot;todo/todo.org&amp;quot; &amp;quot;Email&amp;quot; &amp;quot;Follow Up&amp;quot;)
    &amp;quot;* TODO Follow up with %(eval sent-message-to) on [[mu4e:msgid:%(eval sent-message-id)][%(eval sent-subject)]]
SCHEDULED: %(org-insert-time-stamp (org-read-date nil t \&amp;quot;+3d\&amp;quot;))
%i&amp;quot;))

  (add-hook 'message-sent-hook #'my/org-capture-sent-mail)

  (defun my/org-capture-sent-mail ()
    &amp;quot;Prepare to capture sent mail after window configuration is reset.&amp;quot;
    (let* ((sent-message-id
          (replace-regexp-in-string
           &amp;quot;[&amp;lt;&amp;gt;]&amp;quot; &amp;quot;&amp;quot; (message-fetch-field &amp;quot;Message-Id&amp;quot;)))
           (sent-message-to
            (replace-regexp-in-string &amp;quot; &amp;lt;.*&amp;gt;&amp;quot; &amp;quot;&amp;quot; (message-fetch-field &amp;quot;To&amp;quot;)))
         (sent-subject (or (message-fetch-field &amp;quot;Subject&amp;quot;) &amp;quot;No subject&amp;quot;)))
      (org-capture nil &amp;quot;efu&amp;quot;)
      (add-hook 'mu4e-compose-post-hook #'my/pop-to-buffer-org-capture-mail 99)))

  (defun my/pop-to-buffer-org-capture-mail ()
    (pop-to-buffer
     (car (match-buffers
           (lambda (buffer)
             (equal &amp;quot;efu&amp;quot;
                    (plist-get
                     (buffer-local-value
                      'org-capture-current-plist buffer)
                     :key))))))
    (remove-hook 'mu4e-compose-post-hook #'my/pop-to-buffer-org-capture-mail))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After I send an email, I see the &lt;code&gt;org-capture&lt;/code&gt; pop-up window
pre-filled from the template:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;*** TODO Follow up with John Doe on [[mu4e:msgid:1234576890][Beautiful Snowy Morning]]
SCHEDULED: &amp;lt;2025-02-06 Wed&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If I run &lt;code&gt;org-capture-finalize&lt;/code&gt; (&lt;code&gt;C-c C-c&lt;/code&gt;), then I'll be reminded in
three days to follow up on the email.  Before capturing the reminder,
I can add more information or edit the date if I want to have more or
less time before it shows up on my agenda.  Alternatively, I can run
&lt;code&gt;org-capture-kill&lt;/code&gt; (&lt;code&gt;C-c C-k&lt;/code&gt;) if I don't want to be reminded at all.&lt;/p&gt;
&lt;h2&gt;Technical Details&lt;/h2&gt;
&lt;p&gt;After closing a composition buffer, &lt;code&gt;mu4e&lt;/code&gt; restores your window
configuration in &lt;code&gt;mu4e-compose-post-hook&lt;/code&gt;, a feature which I want to
keep.  However, this means that the &lt;code&gt;org-capture&lt;/code&gt; buffer gets hidden
when the windows are restored.1 The solution above is to add a
self-removing hook function &lt;code&gt;my/pop-to-buffer-org-capture-mail&lt;/code&gt; to
&lt;code&gt;mu4e-compose-post-hook&lt;/code&gt; which pops back to the &lt;code&gt;org-capture&lt;/code&gt; buffer
after the window configuration is restored.&lt;/p&gt;
&lt;h2&gt;Links&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/djcb/mu/issues/2809&quot;&gt;Issue in mu4e tracker with djcb's comments&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://git.sr.ht/~breatheoutbreathein/dotfiles/tree/master/item/.emacs.d/init.el&quot;&gt;My Emacs configuration&lt;/a&gt; (search for &lt;code&gt;my/org-capture-sent-mail&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Footnotes&lt;/h1&gt;
&lt;h4&gt;1&lt;/h4&gt;
&lt;p&gt;I want to interact with the org capture buffer each time I send
an email, so I can edit or cancel the reminder while capturing it.  If
I didn't want to interact with the org capture buffer, I could put
&lt;code&gt;:immediate-finish&lt;/code&gt; in the &lt;code&gt;org-capture&lt;/code&gt; template and get rid of
&lt;code&gt;my/pop-to-buffer-org-capture-mail&lt;/code&gt;.&lt;/p&gt;
</content></entry><entry><title>Query Regexp Replace Lisp Replacement</title><id>https://breatheoutbreathe.in/posts/query-regexp-replace-lisp-replacement.html</id><author><name>Joseph Turner</name><email>contact@breatheoutbreathe.in</email></author><updated>2024-12-19T12:00:00Z</updated><link href="https://breatheoutbreathe.in/posts/query-regexp-replace-lisp-replacement.html" rel="alternate" /><content type="html">&lt;p&gt;I rarely use &lt;code&gt;query-replace-regexp&lt;/code&gt;'s lisp expression replacement
feature, but I came upon a good use for it today!&lt;/p&gt;
&lt;p&gt;Recently, I've been testing out &lt;a href=&quot;https://github.com/bohonghuang/org-srs/&quot;&gt;org-srs&lt;/a&gt; for practicing Mandarin vocab.
So far, it works very well!  When I asked to truncate the serialized
data which gets written to the Org files to reduce diff noise and to
make the &lt;code&gt;state&lt;/code&gt; column fit on my small laptop screen, &lt;a href=&quot;https://github.com/bohonghuang/org-srs/issues/8#issuecomment-2551418929&quot;&gt;the package
author kindly offered a workaround&lt;/a&gt;1, which causes this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Without workaround:

#+NAME: srsitem:2d51efcd-8523-4432-b074-50d1ba4c45b3
| ! | timestamp            | rating |          stability |        difficulty | state     |
|---+----------------------+--------+--------------------+-------------------+-----------|
|   | 2024-12-11T18:37:19Z |        |                0.0 |               0.0 | :new      |
|   | 2024-12-11T18:38:23Z | :again |             0.4072 |            7.2102 | :learning |
|   | 2024-12-11T18:39:05Z | :easy  | 0.9375660842587888 | 6.078123501986166 | :review   |
|   | 2024-12-14T07:07:08Z | :easy  | 16.133939701409084 | 4.972537594025855 | :review   |
| * | 2024-12-30T07:07:08Z |        |                    |                   |           |
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to become this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# With workaround:

#+NAME: srsitem:2d51efcd-8523-4432-b074-50d1ba4c45b3
| ! | timestamp            | rating | stability | difficulty | state     |
|---+----------------------+--------+-----------+------------+-----------|
|   | 2024-12-11T18:37:19Z |        |       0.0 |        0.0 | :new      |
|   | 2024-12-11T18:38:23Z | :again |      0.41 |       7.21 | :learning |
|   | 2024-12-11T18:39:05Z | :easy  |      0.94 |       6.08 | :review   |
|   | 2024-12-14T07:07:08Z | :easy  |     16.13 |       4.97 | :review   |
| * | 2024-12-30T07:07:08Z |        |           |            |           |
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, I already had some existing data in my &lt;code&gt;chinese.org&lt;/code&gt; file.
To truncate that data, I used this lisp expression replacement with
&lt;code&gt;query-replace-regexp&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[0-9]+\.\([0-9]+\) → \,(if (length&amp;gt; \1 2) (format &amp;quot;%0.02f&amp;quot; \#0) \0)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The regexp matches the numbers in the &lt;code&gt;stability&lt;/code&gt; and &lt;code&gt;difficulty&lt;/code&gt;
columns.  My &lt;code&gt;chinese.org&lt;/code&gt; file conveniently contains no other numbers
in decimal-point notation.  The lisp expression checks if the part
after the decimal point contains more than two numbers, since I don't
want &lt;code&gt;0.0&lt;/code&gt; to become &lt;code&gt;0.00&lt;/code&gt;.  If it does, then the &lt;code&gt;format&lt;/code&gt; expression
rounds it to two decimal places.  Otherwise, the match is left as-is.
Note that &lt;code&gt;\#0&lt;/code&gt; in the replacement text converts the match string to a
number, which is required by the &lt;code&gt;%f&lt;/code&gt; format type specifier.&lt;/p&gt;
&lt;p&gt;After replacing all of the numbers in the buffer, I ran a quick
keyboard macro to align all of the &lt;code&gt;srsitem&lt;/code&gt; tables to fit the
narrower contents, but I'll leave that as an exercise to the reader.&lt;/p&gt;
&lt;p&gt;I hope this helps you.  Feel free to &lt;a href=&quot;../contact.md&quot;&gt;contact me&lt;/a&gt; with suggestions,
corrections, or questions!&lt;/p&gt;
&lt;h1&gt;Footnotes&lt;/h1&gt;
&lt;h4&gt;1&lt;/h4&gt;
&lt;p&gt;Workaround to truncate the data:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(cl-defmethod org-srs-algorithm-repeat :around ((_ fsrs-scheduler) _)
  (let ((output (cl-call-next-method)))
    (setf (alist-get 'stability output) (format &amp;quot;%.02f&amp;quot; (alist-get 'stability output))
          (alist-get 'difficulty output) (format &amp;quot;%.02f&amp;quot; (alist-get 'difficulty output)))
    output))
&lt;/code&gt;&lt;/pre&gt;
</content></entry><entry><title>Mirror Worg to a Hyperdrive</title><id>https://breatheoutbreathe.in/posts/mirror-worg-to-a-hyperdrive.html</id><author><name>Joseph Turner</name><email>contact@breatheoutbreathe.in</email></author><updated>2024-12-16T12:00:00Z</updated><link href="https://breatheoutbreathe.in/posts/mirror-worg-to-a-hyperdrive.html" rel="alternate" /><content type="html">&lt;p&gt;During the Q&amp;amp;A session of the &lt;a href=&quot;https://emacsconf.org/2024/talks/hyperdrive/&quot;&gt;EmacsConf 2024 hyperdrive.el talk&lt;/a&gt;, Ihor
Radchenko suggested using hyperdrive to distribute &lt;a href=&quot;https://git.sr.ht/~bzg/worg/&quot;&gt;worg&lt;/a&gt;, the
community-driven Org documentation written in Org format.&lt;/p&gt;
&lt;p&gt;Thanks, Ihor!&lt;/p&gt;
&lt;h1&gt;Mirror &lt;code&gt;worg&lt;/code&gt; to a hyperdrive&lt;/h1&gt;
&lt;p&gt;You can run these commands on a local or remote machine.  I chose to
mirror &lt;code&gt;worg&lt;/code&gt; to a hyperdrive on a remote Debian 12 VPS so that the
content stays available when I turn off my computer.&lt;/p&gt;
&lt;p&gt;Install &lt;code&gt;git&lt;/code&gt;, &lt;code&gt;dtach&lt;/code&gt; and &lt;a href=&quot;https://github.com/holepunchto/drives/&quot;&gt;drives cli&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo apt install git dtach npm
npm install -g drives
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Clone &lt;code&gt;worg&lt;/code&gt;, then create a new corestore and hyperdrive:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git clone https://git.sr.ht/~bzg/worg/
cd worg
drives init
drives touch # Copy hyperdrive public key from output
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Mirror the &lt;code&gt;worg&lt;/code&gt; repository into the newly created hyperdrive:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# drives mirror --dry-run . bpb1bq6sdfajw4bok7k9tdmrq153pbwzcyfkrqrfxw3hhihyddhy # Dry run first
dtach -n /tmp/drives-worg-session drives mirror --live . bpb1bq6sdfajw4bok7k9tdmrq153pbwzcyfkrqrfxw3hhihyddhy # From output of `drives touch`
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that &lt;a href=&quot;https://github.com/holepunchto/drives/?tab=readme-ov-file#mirror-any-drive-into-another&quot;&gt;&lt;code&gt;drives&lt;/code&gt; ignores the &lt;code&gt;.git&lt;/code&gt; folder&lt;/a&gt; out of the box.  Running
the command inside a &lt;code&gt;dtach&lt;/code&gt; session is a quick and dirty way to make
the process persist after we've disconnected from the VPS.  A better
approach would be to write a service file to register the &lt;code&gt;drives&lt;/code&gt;
process with the init system (Debian 12 uses &lt;code&gt;systemd&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;To automatically pull the latest changes from the remote &lt;code&gt;worg&lt;/code&gt;
repository, add a cronjob with &lt;code&gt;crontab -e&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;30 0 * * * git -C ~/worg pull
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Voilá!  &lt;code&gt;worg&lt;/code&gt; is now continuously mirrored to a hyperdrive, which can
be explored, linked to, transcluded, and more using &lt;a href=&quot;https://ushin.org/hyperdrive/hyperdrive-manual.html&quot;&gt;hyperdrive.el&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;hyper://bpb1bq6sdfajw4bok7k9tdmrq153pbwzcyfkrqrfxw3hhihyddhy/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you're viewing this file in Emacs with &lt;code&gt;hyperdrive.el&lt;/code&gt; installed,
follow this link to view the &amp;quot;Introduction to Org-Mode and Worg&amp;quot; in
the hyperdrive mirror:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;hyper://bpb1bq6sdfajw4bok7k9tdmrq153pbwzcyfkrqrfxw3hhihyddhy/index.org#%3A%3A%2AIntroduction%20to%20Org-Mode%20and%20Worg
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I hope this helps you.  Feel free to &lt;a href=&quot;../contact.md&quot;&gt;contact me&lt;/a&gt; with suggestions,
corrections, or questions!&lt;/p&gt;
</content></entry><entry><title>Sourcehut Org Mode README</title><id>https://breatheoutbreathe.in/posts/sourcehut-org-mode-readme.html</id><author><name>Joseph Turner</name><email>contact@breatheoutbreathe.in</email></author><updated>2024-12-10T12:00:00Z</updated><link href="https://breatheoutbreathe.in/posts/sourcehut-org-mode-readme.html" rel="alternate" /><content type="html">&lt;p&gt;Out of the box, Sourcehut only renders &lt;code&gt;README&lt;/code&gt;, &lt;code&gt;README.md&lt;/code&gt;, and
&lt;code&gt;README.markdown&lt;/code&gt;.1&lt;/p&gt;
&lt;h1&gt;Workarounds to render &lt;code&gt;README.org&lt;/code&gt;&lt;/h1&gt;
&lt;p&gt;One proposed workaround is to export &lt;code&gt;README.org&lt;/code&gt; to &lt;code&gt;README.md&lt;/code&gt; (in a
git hook), and then commit &lt;code&gt;README.md&lt;/code&gt;, which will be rendered as HTML
on &lt;code&gt;git.sr.ht&lt;/code&gt;.  Another option is to &lt;a href=&quot;https://man.sr.ht/git.sr.ht/#setting-a-custom-readme&quot;&gt;set a custom README&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here's how we render the &lt;a href=&quot;https://git.sr.ht/~ushin/hyperdrive.el&quot;&gt;README for hyperdrive.el on git.sr.ht&lt;/a&gt; by
setting a custom README:&lt;/p&gt;
&lt;h2&gt;Makefile + build script&lt;/h2&gt;
&lt;p&gt;The Elisp for exporting &lt;code&gt;README.org&lt;/code&gt; to HTML goes in a Makefile, which
simplifies the packaging process, since there's no extra &lt;code&gt;.el&lt;/code&gt; file
for linters and compilers to complain about.  Credit for &lt;a href=&quot;https://git.sr.ht/~ushin/hyperdrive.el/commit/727f1b4b145c777dc5080312b08dc2c8d019a591&quot;&gt;the idea to
use a Makefile&lt;/a&gt; goes to &lt;a href=&quot;https://emacsair.me/&quot;&gt;Jonas Bernoulli&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Add a file called &lt;code&gt;Makefile&lt;/code&gt; at the root of your project
(&lt;a href=&quot;https://git.sr.ht/~ushin/hyperdrive.el/tree/master/item/Makefile&quot;&gt;hyperdrive.el Makefile&lt;/a&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;EMACS := emacs
EMACSQ := $(EMACS) -Q
BATCH := $(EMACSQ) --batch \
          --eval '(setq vc-handled-backends nil)' \
          --eval '(setq org-startup-folded nil)' \
          --eval '(setq org-element-cache-persistent nil)' \
          --eval '(setq gc-cons-threshold (* 50 1000 1000))'

README.html: README.org
        $(BATCH) --find-file $&amp;lt; --eval &amp;quot;(require 'ox-html)&amp;quot; \
          --eval '(org-html-export-to-html nil nil nil t)'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The build script installs &lt;code&gt;hut&lt;/code&gt; and &lt;code&gt;emacs&lt;/code&gt;, clones your repo, exports
&lt;code&gt;README.html&lt;/code&gt;, and sets the custom README.  Add a file called
&lt;code&gt;readme.yml&lt;/code&gt; in the &lt;code&gt;.builds/&lt;/code&gt; subdirectory of your project
(&lt;a href=&quot;https://git.sr.ht/~ushin/hyperdrive.el/tree/master/item/.builds/readme.yml&quot;&gt;hyperdrive.el .builds/readme.yml&lt;/a&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;image: archlinux
oauth: git.sr.ht/REPOSITORIES:RW git.sr.ht/PROFILE:RO
packages:
  - hut
  - emacs-nox
sources:
  - https://git.sr.ht/~ushin/hyperdrive.el
tasks:
  - update-readme: |
      cd hyperdrive.el
      make README.html
      hut git update --readme README.html
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I hope this helps you.  Feel free to &lt;a href=&quot;../contact.md&quot;&gt;contact me&lt;/a&gt; with suggestions,
corrections, or questions!&lt;/p&gt;
&lt;h1&gt;Acknowledgements&lt;/h1&gt;
&lt;p&gt;Thank you to Thorben Günther, for &lt;a href=&quot;https://git.xenrox.net/~xenrox/custom-readme&quot;&gt;this example git repo&lt;/a&gt; which shows
how to set a custom README.&lt;/p&gt;
&lt;h1&gt;Footnotes&lt;/h1&gt;
&lt;h4&gt;1&lt;/h4&gt;
&lt;p&gt;Requests for Org-mode README rendering support on git.sr.ht:
&lt;a href=&quot;https://lists.sr.ht/~sircmpwn/sr.ht-discuss/%3Cfe7aa296-9c90-463d-b4e6-50eeb7e57428@localhost%3E&quot;&gt;Mar 2020&lt;/a&gt; &lt;a href=&quot;https://lists.sr.ht/~sircmpwn/sr.ht-discuss/%3C5a7e210f869589d6abd0c0b165192b32c6325068.camel@zaclys.net%3E&quot;&gt;Feb 2021&lt;/a&gt; &lt;a href=&quot;https://lists.sr.ht/~sircmpwn/sr.ht-discuss/%3C8830212A-2DDD-4BCA-AB0C-534D9B6B1EC7@traduction-libre.org%3E&quot;&gt;Oct 2021&lt;/a&gt; &lt;a href=&quot;https://lists.sr.ht/~sircmpwn/sr.ht-discuss/%3C87fsp4szx5.fsf@complete.org%3E#%3C87fsp4szx5.fsf@complete.org%3E&quot;&gt;Jan 2022&lt;/a&gt; &lt;a href=&quot;https://lists.sr.ht/~sircmpwn/sr.ht-discuss/%3Cm2wndqpgdf.fsf@New-Vulcan.localdomain%3E#%3Cm2wndqpgdf.fsf@New-Vulcan.localdomain%3E&quot;&gt;June 2022&lt;/a&gt; &lt;a href=&quot;https://lists.sr.ht/~sircmpwn/sr.ht-discuss/%3C87sez21jhj.fsf@gilgamesh.mail-host-address-is-not-set%3E#%3CD0Z8J5N4PLG0.14FO1M1WUQO7C@cepl.eu%3E&quot;&gt;May 2024&lt;/a&gt; &lt;a href=&quot;https://lists.sr.ht/~sircmpwn/sr.ht-discuss/%3C87cyib79ad.fsf@%3E&quot;&gt;Dec 2024&lt;/a&gt;&lt;/p&gt;
</content></entry></feed>