1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
//! Live-reload signal for `xr dev` - the endpoint a page's injected script
//! (`larust_view::runtime::View`, and the dev-mode-only script
//! `error_pages` injects into the framework's own 404/500 fallback pages)
//! connects to. Three independent signals share this one endpoint:
//!
//! - A **full rebuild handoff** (a new generation actually took over) never
//! sends a real event here at all - the running process itself gets
//! replaced (see `lifecycle::handoff`), so the client's own
//! `EventSource` connection drops and reconnects against the new
//! process. The injected script watches *that* (lost, then successfully
//! reconnected) as its "a new build is up" signal - nothing needs to
//! originate from the server side for this case.
//! - A **static-asset-only change** (`public/`, e.g. a stylesheet) never
//! needs a rebuild at all - `xr dev` skips straight to sending the admin
//! channel's `RELOAD_ASSETS` command (see `lifecycle::admin`) to whatever
//! is currently running, which calls [`broadcast_asset_reload`] here. That
//! pushes a real, named `reload-assets` SSE event to every connected tab
//! so it can refresh just its stylesheets in place, with no full page
//! navigation and no server process restart at all.
//! - A **rebuild that doesn't (yet) replace the running process** - every
//! rebuild after the very first one, for the whole time it's in flight,
//! plus a rebuild that fails outright. The process this endpoint is
//! already connected to is, by design, the *last known-good* one and
//! keeps right on serving throughout (that's the zero-downtime guarantee
//! `dev.rs`'s own doc comment describes) - so its SSE connection never
//! drops, and a request that 404s because the route only exists in the
//! code currently being compiled looks identical to a genuine mistake
//! with no signal at all otherwise. `xr dev` sends the admin channel's
//! `BUILD_STATUS` command (`building`/`failed`) at the start/end of every
//! such rebuild, which calls [`broadcast_build_status`] here, pushing a
//! named `build-status` SSE event a connected tab turns into a small,
//! non-disruptive banner.
use ;
use Stream;
use Infallible;
use OnceLock;
use broadcast;
use BroadcastStream;
use StreamExt;
/// What a connected dev-reload client is told about, beyond the implicit
/// "connection dropped and reconnected" handoff signal (see this module's
/// own doc comment).
/// Every connected dev-reload client subscribes to this. Sized generously
/// relative to how rarely a save actually happens - a lagged receiver just
/// drops the stale signal and waits for the next one (see `handler`'s own
/// `filter_map`), which is harmless: every event here is a status update
/// superseded by whatever comes next, never something that needs to be
/// replayed in full.
/// Called from the admin channel (`lifecycle::admin`) when it receives
/// `RELOAD_ASSETS` - i.e. `xr dev` decided a change didn't need a rebuild.
/// Best-effort and silent when nobody's listening: no browser tab currently
/// connected simply means there's nothing to refresh right now.
/// Called from the admin channel when it receives `BUILD_STATUS <status>` -
/// i.e. `xr dev` starting or finishing a rebuild that (so far) hasn't
/// replaced this process. `status` is forwarded to connected clients
/// verbatim as the `build-status` SSE event's data - see this module's own
/// doc comment for the two values `xr dev` actually sends
/// (`"building"`/`"failed"`). Best-effort and silent when nobody's
/// listening, same as [`broadcast_asset_reload`].
/// A stream that never resolves on its own - `Sse`'s `KeepAlive` layer
/// keeps the connection alive on its own timer regardless of what the
/// underlying stream produces. Real items only ever arrive via
/// [`broadcast_asset_reload`]/[`broadcast_build_status`]; a lagged receiver
/// (fell behind by more than the channel's capacity) is treated the same
/// as "no event yet" rather than propagated as a stream error, since a
/// missed intermediate status update is never worth tearing down the
/// connection over.
pub async