Euler source The code that runs the solver shell and this site, version 4.34.2 Repository on GitHub

The message chip: what wants your attention, and the way to read it.

65 lines of HTML Raw file Open on GitHub

{# The message chip (web-server-guide § Messaging): what wants your attention, and the way to
   read it. Lives in the fixed header inside `.app-who`, between the terminal and git chips —
   all three are state about *you* rather than about the page in the pane.

   There is no messages page, and that is the design. The spool exists so a *command* can
   tell staff something they must act on (`user` filing a key-authorization request), not so
   people can write to each other in a browser. So this chip reports, and every verb on it
   **types a real command into the web shell** — the same move the git chip's verbs and the
   account page's tool rows make, for the same reason: one execution path, one audit trail,
   and the command's own `requires()` is the boundary whatever this menu shows.

   Same <details class="menu"> chassis as Actions, git and the user pill: one menu idiom in
   the header, not four.

   `msg_spool` (render._context) is the switch: True only on a tier that actually serves
   /messages, so the auth service — which renders this same header and has no spool — gets
   the inert chip instead of a dead fetch. It is a plain flag, never a count: the header must
   not cost a spool read per navigation, so the number and the rows arrive from this
   element's own fetch a moment later.

   **This element swaps its CONTENTS, never itself** (`hx-swap="innerHTML"`, and the body
   lives in `_msg_menu.html`). With `outerHTML` the chip replaced the <details> on every
   fetch, and two things broke: the fresh element's own `hx-trigger="load"` fired again, so
   it re-fetched in a tight loop forever; and each swap reset `open`, so the menu shut a few
   milliseconds after any click. Keeping the element also keeps the `.term-offline` class
   site.js paints on it. The git chip has since settled on the same shape, for the second
   of those reasons: it too refreshes on being opened, and a chip that replaced itself
   would close the menu it had just been asked to answer.

   Three triggers, no polling:
   · `load` — once per document, so a full page load is always current;
   · `euler:message from:body` — the delivery nudge, pushed by euler-msg to this user's
     instance and relayed over the terminal's WebSocket as a TEXT frame (terminal.js →
     site.js). That is what makes a message arriving *now* show up now;
   · `toggle[this.open]` — opening the panel. The two above are pushes, and a push can be
     missed: the nudge rides the terminal's WebSocket, so a disconnected or hidden terminal
     silently drops it, and `load` has not fired since the last full document load. Opening
     the menu is the one moment a person is *asking* what is waiting, and it is exactly when
     the answer must not be a count from twenty minutes ago. The filter is what keeps it to
     opening — a bare `toggle` fires on close too, spending a fetch on a panel nobody is
     looking at any more. htmx calls the filter with the element as `this` (`r.call(elt,
     evt)`), so `this.open` reads the state the event just produced.

     `throttle:1s` because the trigger is a pointer away from being held down: it fires the
     first open and drops the rest of the second. Nobody reopens a menu inside a second
     expecting a different number, so it cannot show anything stale a person would notice. #}
{% if subject and msg_spool %}
<details id="msg-chip" class="menu msg-menu term-menu"
         hx-get="/messages" hx-swap="innerHTML"
         hx-trigger="load, euler:message from:body, toggle[this.open] throttle:1s">
  {% include "_msg_menu.html" %}
</details>
{% else %}
{# Inert, never absent — signed out, or a tier with no spool. The header keeps its shape
   before the visitor is let in, exactly as the terminal and git chips do. No hx- attributes:
   the auth tier has no /messages route, and a dead fetch would be a promise nothing keeps. #}
<span class="msg-chip is-locked" aria-disabled="true" title="no messages">
  <svg class="msg-glyph" viewBox="0 0 24 24" aria-hidden="true">
    <rect x="2.5" y="5" width="19" height="14" rx="2" fill="none"
          stroke="currentColor" stroke-width="2"/>
    <path d="M3.5 7l8.5 6 8.5-6" fill="none" stroke="currentColor" stroke-width="2"
          stroke-linecap="round" stroke-linejoin="round"/>
  </svg>
</span>
{% endif %}