You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
New shared dashboard hook useVisibilityGatedLink (src/shared/hooks) — the SSE analog of useDashboardGraph: it owns a RemoteLink's whole connection lifecycle (mount via mountExospine, close while inactive, and RECONNECT the same link from its last seen offset on refocus instead of tail-dropping the gap accumulated while hidden). Callers supply mountNodes / isActive / onConnect. Extracts the resume-on-refocus guard that had been copy-pasted inline into every streaming dashboard; useTopicProbeStream now uses it, and the newspack-event-logger-nodes Request Log / Error Log / Gyroscope dashboards consume it via the @newspack-nodes/shared alias. (Raw Logs stays custom — its list_logs-driven, user-selected-log flow isn't visibility-gated connect.)
Two shared substrate traits — Offsetlog_Cursor (durable offsetlog read/commit) and Dead_Letter_Queue (poison quarantine + fair-shot / crash-crawl accounting) — extracted from Consumer_Node and now used by both Consumer_Node and Remote_Source_Node, so the durable-cursor and dead-letter logic has one implementation. See ADR-12.
Changed
Workers now self-respawn via the same 10ms raw-curl fire-and-forget POST the supervisor uses, instead of wp_remote_post( timeout: 1, blocking: false ). The shared helper (Core::fire_and_forget_post(), with the single Core::$curl_exec test seam) was hoisted out of Supervisor so both spawn paths share one implementation; Worker_Base::self_respawn() was on the slower path because WordPress's Requests Curl transport floors any sub-second timeout to a full second, defeating the fire-and-forget contract a dying worker needs. The POST body (type/partition/nonce) is unchanged, so spawn HMAC/nonce validation is unaffected.
Command_Auth now signs/verifies the message's positional Message::TIMESTAMP instead of a separate ts field smuggled into the auth envelope, and Message::new_message()'s default timestamp is the cached Core::$now ?: microtime( true ) (float seconds). Sign/verify cast the timestamp to integer seconds (the freshness window's granularity) under an is_numeric guard, so the same wire value truncates identically on both sides.
Remote_Source now applies the full dead-letter lifecycle to its pulled stream. A stream message whose downstream forward throws blocks the head (nothing past it forwards; the committed cursor freezes) and climbs attempts across worker cycles; at COOP_MAX_ATTEMPTS it is quarantined to a :deadletter sibling and the cursor advances past it — a durable dlq offsetlog marker makes that advance safe even when the poison is the last/idle message (no re-quarantine on recycle). A hard crash enters a per-message-checkpoint crawl that resets to the baseline after one clean checkpoint interval, and Remote_Source's cursor is now checkpointed at worker shutdown.
Fixed
READY-state notifications now carry the emitting node's name.Consumer, Partition, and Topic nodes called set_state( 'READY' ) with no payload, so a registrant on the READY event received an empty value and couldn't tell which node became ready. They now pass $this->name, so callback registrants get the node's identity.
Streaming dashboards: the "Xs ago" stream-staleness indicator works again. Raw Logs (and, in newspack-event-logger-nodes, Request/Error/Gyroscope) drove staleness off Core.node('_sse').lastEventTime, but _sse is not a registered browser node — RemoteLink composes its SseIn UNNAMED — so the lookup was always undefined and the indicator was silently dead. RemoteLink now exposes a lastEventTime() passthrough (mirroring resumePositions()/pid()), and the dashboards read it off the link's own registered name (rawlogs:link, etc.). The component tests had masked the bug by injecting a fake _sse node; they now inject the real link node.
Topology editor: nodes are no longer dimmed in edit mode. The graph canvas dims quiet ("idle") nodes to opacity: 0.55 in live mode, keyed off each node's message rate. Edit mode has no live rate stream, so every node's rate read as undefined → idle → dimmed, making the whole draft graph look greyed out. The idle-dim is now gated to live mode only (!editMode); a stale comment claiming edit mode passes no rate ref — it does — was corrected.
Topics dashboards: the 24h chart no longer stretches its axis to days. The durable topicprobe log replays further back than the chart's 24h window, so the view accumulated every replayed record and the axis spanned however far the log reached (2.5 days in practice). The topicprobe:view node now enforces a fixed 24h wall-clock window: records older than 24h are dropped on arrival, and each consumer's series is pruned of aged-out samples at every publish — across all consumers, so an idle source's stale tail ages out by wall-clock time, not only when it next produces. The per-consumer ring cap is now held one sample above the window so the time-prune, not the ring, is the boundary.
Topics dashboards: the backlog / cache-size panels no longer draw a phantom sawtooth. Each worker process runs its own 15s TopicProbe at an independent phase, so topics sampled at offset instants; the chart aligned them on the raw union of instants and zero-filled every gap — which is correct for a rate but turns a level gauge (backlog, cache size) into a smooth [3MB, 0, 3MB, 0…] sawtooth under curveMonotoneX. Samples now snap to an epoch-aligned 15s bucket grid (so cross-process sweeps land in the same buckets), and level metrics carry the last value forward across empty buckets instead of dropping to 0 (fill: 'hold'); rates still zero-fill. The real backlog now reads as the smooth decline it is.
Command freshness is anchored to the server clock at the sign boundary.HTTP_In now stamps Message::TIMESTAMP = Core::$now immediately before Command_Auth::sign(), so the freshness window no longer depends on the browser's wall clock — a dashboard client whose clock is skewed >10–20s is no longer silently rejected (timestamp out of range).
CLI error messages are sanitized for the terminal, not HTML-escaped.wp nodes cli exception messages ran through esc_html(), rendering quotes as ' in the shell. They now strip control characters (defanging ANSI injection from an untrusted worker id) while leaving the literal text intact.
Remote_Source stream routing/forwarding hardened:SSE_In::forward() now honors a failed stamp_message() (drops an over-MAX_FROM_SIZE message instead of forwarding it unstamped); Remote_Source discriminates a relayed stream message by TYPE (TM_BYTESTREAM/TM_STRUCT) rather than a fragile FROM-prefix; and a null/unwired downstream sink now fails loud instead of silently dropping the stream while advancing the cursor.