-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.html
422 lines (420 loc) · 26.5 KB
/
index.html
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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
<!DOCTYPE html>
<html>
<head>
<title>HTTP/X</title>
<link rel="icon" href="/httpx_logo.svg" type="image/svg+xml">
<style>
body {
max-width: 600px;
margin: auto;
padding: 32px;
font-family: 'Courier New', Courier, monospace;
}
</style>
<!-— twitter card tags additive with the og: tags -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:domain" value="HttpX.org" />
<meta name="twitter:title" value="HTTP/X" />
<meta name="twitter:description" value="Like HTTP but in reverse. HTTP/X is a protocol for building rich web applications where state and logic operate server-side instead of in the browser. " />
<meta name="twitter:image" content="https://httpx.org/httpx_logo.png" />
<meta name="twitter:url" value="https://httpx.org/" />
<!-— facebook open graph tags -->
<meta property="og:type" content="website" />
<meta property="og:url" content="https://httpx.org/" />
<meta property="og:title" content="HTTP/X" />
<meta property="og:description" content="Like HTTP but in reverse. HTTP/X is a protocol for building rich web applications where state and logic operate server-side instead of in the browser." />
<meta property="og:image" content="https://httpx.org/httpx_logo.png" />
</head>
<body>
<img src="httpx_logo.svg" style="height: 192px; padding: 32px; width: 100%;" />
<h1 id="http-x-rfc-">HTTP/X (RFC)</h1>
<p>
HTTP/X is a protocol for webapps that enables state and logic to operate
server-side instead of in the browser.
</p>
<p>
The X is meant to represent the marriage of two concepts: bidirectional
communication + a predefined instruction set designed for efficient
manipulation of the DOM from over the wire.
</p>
<p>
Think of it like HTTP but in reverse. Instead of the browser driving the
communication, HTTP/X puts the server in the driver's seat.
</p>
<h3 id="table-of-contents">Table of Contents</h3>
<ul>
<li><a href="#advantages">Advantages</a></li>
<li><a href="#why-a-spec">Why a Spec?</a></li>
<li><a href="#terminology">Terminology</a></li>
<li><a href="#bidirectional-protocols">Bidirectional Protocols</a></li>
<li><a href="#lifecycle">Lifecycle</a></li>
<li>
<a href="#graceful-fallback-to-http">Graceful Fallback to HTTP</a>
</li>
<li><a href="#sentinels">Sentinels</a></li>
<li><a href="#mutation-instruction-set">Mutation Instruction Set</a></li>
<li><a href="#layout-reuse">Layout Reuse</a></li>
<li><a href="#more-info">More Info</a></li>
</ul>
<h2 id="advantages">Advantages</h2>
<p>HTTP/X brings 4 main benefits:</p>
<ol>
<li>
<p>
<strong>New Languages</strong><br />
When both browser events and DOM manipulations can be operated
server-side, it fully removes your codebase's dependency on JavaScript
to drive the micro-interactions necessary in any modern webapp. This
frees developers to choose languages based on their merits, not the
browser's restrictions. (To enhance your language of choice, check out
<a href="https://zeroscript.org">ZeroScript</a>, syntactic sugar for
easily templating HTML in any language.)
</p>
</li>
<li>
<p>
<strong>No API Needed</strong><br />
Since the business logic is never downloaded to the browser, there's
no need to fetch data from a separate API thus eliminating
additional dependencies to build and support.
</p>
</li>
<li>
<p>
<strong>Zero Payload Issues</strong><br />
The amount of JavaScript needed to run HTTP/X is not only shockingly
tiny, it stays the exact same size, even if the complexity of your
webapp grows to hundreds of megabytes (as native apps often do). This
lifts the burden of toiling over optimizations like code splitting,
etc and eliminates needing to compromise delightful UX in the name of
lighthouse scores.
</p>
</li>
<li>
<p>
<strong>Globally Shared State</strong><br />
When state must be sent to each individual browser so the UI can react
to its changes, there will inevitably be a drift in consistency and is
(by default) limited to a user's own local actions. When the state
lives server-side, reacting to changes by other users or other data
sources is trivial to support as it's the default programming model.
</p>
</li>
</ol>
<h2 id="why-a-spec-">Why a Spec?</h2>
<p>
This is NOT intended to be implemented by browsers or engines nor is it a
proposal for the HTTP spec itself. The reason it's not just a simple
library is because it's meant to be implemented across a number of
languages. Having a consistent implementation across languages
<a href="https://rylan.io/zero-new-syntax-to-learn/"
>reduces framework fatigue</a
>
and makes learned skills transferable from language to language.
</p>
<h2 id="terminology">Terminology</h2>
<ul>
<li>
<strong>Connection</strong> - A connection refers to a single WebSocket
connection (or other similar protocol's version of a connection).
Specifically it correlates to a single browser tab or single browser
window and isn't shared across multiple instances.
</li>
<li>
<strong>Session</strong> - A session is cookie-based and is shared
across multiple tabs/windows of the same web browser. This is not the
case for users that run multiple browser vendors at the same time. A
user might authenticate into the same account across multiple browser
vendors, but while they may share the same account, they do NOT share
the same session. Authentication is an application concern and is
outside the scope of HTTP/X.
</li>
<li>
<strong>DOM mutation</strong> - Any change to the browser's Document
Object Model is considered a DOM mutation. Primarily this is done by
setting an element's nodeValue for the sake of precision and performance
but technically also includes HTML partials.
</li>
<li>
<strong>HTML partial</strong> - This is a specific type of DOM mutation
but instead of changing properties on single elements at a time, it
replaces a larger tree of elements by brute force using outerHTML.
</li>
</ul>
<h2 id="bidirectional-protocols">Bidirectional Protocols</h2>
<p>
Unlike most other web frameworks where the use of bidirectional protocols
is optional or “nice-to-have”, HTTP/X takes a hard dependency on it.
HTTP/X primarily uses WebSockets but should fall back to other similar
protocols if necessary such as:
</p>
<ul>
<li><a href="https://en.wikipedia.org/wiki/WebSocket">WebSockets</a></li>
<li>
<a href="https://en.wikipedia.org/wiki/Server-sent_events"
>Server Sent Events</a
>
</li>
<li>
<a href="https://en.wikipedia.org/wiki/Comet_(programming"
>Forever Frame</a
>)
</li>
<li>
<a href="https://en.wikipedia.org/wiki/Comet_(programming"
>Ajax long polling</a
>)
</li>
</ul>
<p>
The fallback order is not crucial nor is supporting every protocol. Most
language ecosystems already have
<a
href="https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API#tools"
>libraries</a
>
that handle this level of transport transparently and their use is greatly
encouraged.
</p>
<h2 id="lifecycle">Lifecycle</h2>
<svg id="diagram" width="100%" xmlns="http://www.w3.org/2000/svg" style="max-width: 221.3828125px;" viewBox="-48 -48 221.3828125 318.5" aria-roledescription="flowchart-v2" height="318.5" preserveAspectRatio="xMinYMin"><style>#diagram{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#diagram .error-icon{fill:#552222;}#diagram .error-text{fill:#552222;stroke:#552222;}#diagram .edge-thickness-normal{stroke-width:2px;}#diagram .edge-thickness-thick{stroke-width:3.5px;}#diagram .edge-pattern-solid{stroke-dasharray:0;}#diagram .edge-pattern-dashed{stroke-dasharray:3;}#diagram .edge-pattern-dotted{stroke-dasharray:2;}#diagram .marker{fill:#333333;stroke:#333333;}#diagram .marker.cross{stroke:#333333;}#diagram svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#diagram .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#diagram .cluster-label text{fill:#333;}#diagram .cluster-label span,#diagram p{color:#333;}#diagram .label text,#diagram span,#diagram p{fill:#333;color:#333;}#diagram .node rect,#diagram .node circle,#diagram .node ellipse,#diagram .node polygon,#diagram .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#diagram .flowchart-label text{text-anchor:middle;}#diagram .node .label{text-align:center;}#diagram .node.clickable{cursor:pointer;}#diagram .arrowheadPath{fill:#333333;}#diagram .edgePath .path{stroke:#333333;stroke-width:2.0px;}#diagram .flowchart-link{stroke:#333333;fill:none;}#diagram .edgeLabel{background-color:#e8e8e8;text-align:center;}#diagram .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#diagram .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#diagram .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#diagram .cluster text{fill:#333;}#diagram .cluster span,#diagram p{color:#333;}#diagram div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#diagram .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#diagram :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}</style><g><marker id="diagram_flowchart-pointEnd" class="marker flowchart" viewBox="0 0 10 10" refX="6" refY="5" markerUnits="userSpaceOnUse" markerWidth="12" markerHeight="12" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker><marker id="diagram_flowchart-pointStart" class="marker flowchart" viewBox="0 0 10 10" refX="4.5" refY="5" markerUnits="userSpaceOnUse" markerWidth="12" markerHeight="12" orient="auto"><path d="M 0 5 L 10 10 L 10 0 z" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker><marker id="diagram_flowchart-circleEnd" class="marker flowchart" viewBox="0 0 10 10" refX="11" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></circle></marker><marker id="diagram_flowchart-circleStart" class="marker flowchart" viewBox="0 0 10 10" refX="-1" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></circle></marker><marker id="diagram_flowchart-crossEnd" class="marker cross flowchart" viewBox="0 0 11 11" refX="12" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" class="arrowMarkerPath" style="stroke-width: 2; stroke-dasharray: 1, 0;"></path></marker><marker id="diagram_flowchart-crossStart" class="marker cross flowchart" viewBox="0 0 11 11" refX="-1" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" class="arrowMarkerPath" style="stroke-width: 2; stroke-dasharray: 1, 0;"></path></marker><g class="root"><g class="clusters"></g><g class="edgePaths"><path d="M50.32,52L47.351,56.167C44.382,60.333,38.443,68.667,35.473,78.542C32.504,88.417,32.504,99.833,32.504,111.25C32.504,122.667,32.504,134.083,34.961,143.239C37.417,152.395,42.331,159.289,44.788,162.737L47.245,166.184" id="L-State-UI-0" class="edge-thickness-normal edge-pattern-solid flowchart-link LS-State LE-UI" style="fill:none;" marker-end="url(#diagram_flowchart-pointEnd)"></path><path d="M87.379,170.5L90.348,166.333C93.318,162.167,99.256,153.833,102.226,143.958C105.195,134.083,105.195,122.667,105.195,111.25C105.195,99.833,105.195,88.417,102.739,79.261C100.282,70.105,95.368,63.211,92.911,59.763L90.455,56.316" id="L-UI-State-0" class="edge-thickness-normal edge-pattern-solid flowchart-link LS-UI LE-State" style="fill:none;" marker-end="url(#diagram_flowchart-pointEnd)"></path></g><g class="edgeLabels"><g class="edgeLabel" transform="translate(32.50390625, 111.25)"><g class="label" transform="translate(-32.50390625, -9.25)"><foreignObject width="65.0078125" height="18.5"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;"><span class="edgeLabel">mutation</span></div></foreignObject></g></g><g class="edgeLabel" transform="translate(105.1953125, 111.25)"><g class="label" transform="translate(-20.1875, -9.25)"><foreignObject width="40.375" height="18.5"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;"><span class="edgeLabel">event</span></div></foreignObject></g></g></g><g class="nodes"><g class="node default default flowchart-label" id="flowchart-State-8" data-node="true" data-id="State" transform="translate(68.849609375, 26)"><rect class="basic label-container" style="" rx="0" ry="0" x="-35.4765625" y="-26" width="70.953125" height="52"></rect><g class="label" style="" transform="translate(-27.9765625, -18.5)"><rect></rect><foreignObject width="55.953125" height="37"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;"><span class="nodeLabel">State<br>(server)</span></div></foreignObject></g></g><g class="node default default flowchart-label" id="flowchart-UI-9" data-node="true" data-id="UI" transform="translate(68.849609375, 196.5)"><rect class="basic label-container" style="" rx="0" ry="0" x="-41.8984375" y="-26" width="83.796875" height="52"></rect><g class="label" style="" transform="translate(-34.3984375, -18.5)"><rect></rect><foreignObject width="68.796875" height="37"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;"><span class="nodeLabel">UI<br>(browser)</span></div></foreignObject></g></g></g></g></g></svg>
<p>
HTTP/X leverages
<a href="https://developer.android.com/jetpack/compose/architecture#udf"
>unidirectional data flow</a
>, a familiar pattern where state flows down and events flow up which
enables decoupling views that display state in the UI from the parts of
the app that store and change state. This fits very naturally in a web
context since browsers use objects to encapsulate their events making them
trivial to marshall across the wire to server-side event handlers. The
typical lifecycle goes as follows:
</p>
<ol>
<li>The browser performs a GET request.</li>
<li>
Server responds with a 200 status code and regular, old fashioned HTML.
Some elements in this HTML might be appended with
<a href="#sentinels">sentinels</a> for precision-manipulation of mutable
elements.
</li>
<li>
The browser displays HTML and establishes a
<a href="#bidirectional-protocols">bidirectional channel</a> with the
server (<a href="#bidirectional-protocols">usually a WebSocket</a>).
</li>
<li>
Any event listeners created are operated by serializing the event into
JSON, MessagePack (or other) and sending to the server over its
bidirectional connection.
</li>
<li>
The server is not obligated to respond to these events in any way. Since
this is an always-on, bidirectional channel, the server can push DOM
mutations at any time. Any changes to the UI state that result in DOM
mutations will trigger mutation instructions to be pushed to the
browser. While this does frequently occur due to state-changes made by
server-side event handlers, this can just as easily occur due to
external sources like realtime database listeners or interactions from
other users mutating “shared state.”
</li>
<li>
Subsequent GETs cause the connection to be closed and the whole process
starts over again EXCEPT in cases where the layout has been configured
for reuse. This technique prevents GETs from navigating away from the
current page thus reusing the connection. Executing the route handler
then causes the server to set its desired state changes for that route
thus triggering more mutation instructions to be pushed to the browser.
</li>
</ol>
<h2 id="graceful-fallback-to-http">Graceful Fallback to HTTP</h2>
<p>
HTTP/X requires a bidirectional channel like WebSockets, SSE or
long-polling in order to operate fully.
</p>
<p>
In the event where a bidirectional channel cannot be established, HTTP/X
must resort to a request/response protocol by POSTing browser events to
the server and awaiting for DOM mutations in the response. This means that
the server cannot react to state changes unless it occurred as a part of
the event handler or initial GET of that specific tab/window. This
HTTP-only mode should be a rare occurrence and can be disabled for webapps
where this service degradation is not suitable.
</p>
<p>
One use case that is not rare, however, is robot traffic. HTTP/X must be
able to decipher between legitimate traffic and robot traffic. This could
be from search engines, unfurling services, or even curl.
</p>
<p>
In these use cases, there is no opportunity to establish a bidirectional
channel for “follow up DOM mutations.” This means it's not possible to
first send a non-blocking response to the initial GET and follow up with
DOM mutations once async data becomes available. Instead, in HTTP-only
mode the initial GET must wait until all the data needed for rendering a
full HTML page is collected before writing to the network stream. This
also means that any data listeners connected to any realtime data sources
must know not to listen for changes and instead fetch just once for that
connection.
</p>
<h2 id="sentinels">Sentinels</h2>
<p>
HTTP/X is NOT a protocol from swapping HTML partials. While that is
possible, HTTP/X is primarily focused on replacing individual nodeValues.
The goal is to provide the maximum level of performance and precision.
</p>
<p>
The server needs an efficient way to target changes to specific elements
in a sea of DOM nodes. Sentinels are applied transparently so the
application developer isn't forced to use id="..." repeatedly or
in ways that are incompatible with styling or repetitive components. A
sentinel is a tiny <code><script></code> block that references its
prior sibling and saves it in a variable in the DOM's global space.
</p>
<blockquote>
<p>
[!Important] Sentinels are not needed for every node – only the ones
that can change.
</p>
</blockquote>
<p>Here's a simplistic example (excluding a few edge cases):</p>
<pre><code class="lang-html"><span class="hljs-tag"><<span class="hljs-name">script</span>></span><span class="javascript">
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">register</span>(<span class="hljs-params">id</span>) </span>{
<span class="hljs-keyword">this</span>[id] = <span class="hljs-built_in">document</span>.currentScript.previousSibling;
}
</span><span class="hljs-tag"></<span class="hljs-name">script</span>></span>
</code></pre>
<p>Register a tag:</p>
<pre><code class="lang-html"><span class="hljs-tag"><<span class="hljs-name">p</span>></span>This is a paragraph.<span class="hljs-tag"></<span class="hljs-name">p</span>></span><span class="hljs-tag"><<span class="hljs-name">script</span>></span><span class="undefined">register(“slot0”)</span><span class="hljs-tag"></<span class="hljs-name">script</span>></span>
</code></pre>
<p>
Register text: To target a substring of text inside a larger text node,
you must isolate it with a “blank” node so that its nodeValue can be
updated in isolation to the full body of text.
</p>
<pre><code class="lang-html"><span class="hljs-tag"><<span class="hljs-name">p</span>></span>Greetings <span class="hljs-comment"><!-- --></span>human<span class="hljs-tag"><<span class="hljs-name">script</span>></span><span class="undefined">register(“slot1”)</span><span class="hljs-tag"></<span class="hljs-name">script</span>></span>, and welcome!<span class="hljs-tag"></<span class="hljs-name">p</span>></span>
</code></pre>
<p>Register an attribute:</p>
<pre><code class="lang-html"><span class="hljs-tag"><<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">”text”</span> <span class="hljs-attr">value</span>=<span class="hljs-string">””</span> /></span><span class="hljs-tag"><<span class="hljs-name">script</span>></span><span class="undefined">register(“slot2”, “value”)</span><span class="hljs-tag"></<span class="hljs-name">script</span>></span>
</code></pre>
<blockquote>
<p>
[!Note] While HTTP/X is made to work seamlessly with
<a href="https://zeroscript.org">ZeroScript</a>, it is possible to
retrofit the protocol into other preexisting templating engines.
</p>
</blockquote>
<h2 id="mutation-instruction-set">Mutation Instruction Set</h2>
<p>
The instruction set needed for mutating the DOM from the server-side is
small – only 3 instructions. Here are the formats the server responds with
which directly targets functions already in the DOM.
</p>
<ul>
<li>
<p>Element values</p>
<pre><code class="lang-json">{ <span class="hljs-attr">"id"</span>: <span class="hljs-string">"slot0"</span>, <span class="hljs-attr">"value"</span>: <span class="hljs-string">"anything"</span> }
</code></pre>
</li>
<li>
<p>Element attributes</p>
<pre><code class="lang-json">{ <span class="hljs-attr">"id"</span>: <span class="hljs-string">"slot1"</span>, <span class="hljs-attr">"attribute"</span>: <span class="hljs-string">"anything"</span> }
</code></pre>
</li>
<li>
<p>HTML partials</p>
<pre><code class="lang-json">{ <span class="hljs-attr">"id"</span>: <span class="hljs-string">"slot2"</span>, <span class="hljs-attr">"html"</span>: <span class="hljs-string">"<p>any <b>HTML</b> string<p>"</span> }
</code></pre>
</li>
</ul>
<h2 id="layout-reuse">Layout Reuse</h2>
<p>
HTTP/X includes a unique way to reuse documents of the same layout between
multiple GET requests without needing to rebuild the entire DOM. Granted,
a browser's initial GET request to any URL must return regular, old
fashioned, vanilla HTML with a 200 HTTP status code. But once it
establishes a bidirectional transport for communication, all subsequent
GET requests to a route using the same shared layout can take a different
approach.
</p>
<p>
To accomplish this, the server must respond with a 204 - No Content status
code. This indicates that the browser should not replace the current page
with a new page. The server can then alter the UI state held in its
memory, which may or may not trigger DOM mutations to be pushed to the
browser over the bidirectional channel.
</p>
<p>
For example, imagine starting by visiting example.com/posts which would
return basic HTML with a 200 status code, and establish a bidirectional
channel with the server. If the user then clicked on
example.com/posts/featured, the server would know, since it already has a
dedicated connection established for this request and that it needs to
transition between two states of the same layout, it could respond with a
204 No Content, and then adjust its UI state such that only the featured
posts are being shown.
</p>
<p>
This has the nice side effect of treating URLs like “bookmarks for state”
instead of “pointers to documents.”
</p>
<p>
Please note that proper UX might dictate that in some cases, while a
layout CAN be shared, sometimes it shouldn't. An example of this is
lateral moves to the same routes of different IDs, like from
example.com/products/1 to example.com/products/2. The user might be
scrolled way down into the related products section and click on
product/2. In this use case, the layout shouldn't be reused, nor does
scrolling to the top fix the expected behavior. Most mature design systems
would dictate that these layouts be transitioned using either a
<a
href="https://m3.material.io/styles/motion/transitions/transition-patterns"
>lateral or forward-and-backward</a
>
approach.
</p>
<p>
The specifics of this configuration is outside the scope of HTTP/X; it's
an implementation detail for the application framework.
</p>
<h2 id="more-info">More Info</h2>
<h3 id="engage">Engage</h3>
<ul>
<li>
<p>
Contribute:<br />
<a href="https://github.com/schmylan/httpx"
>github.com/schmylan/httpx</a
>
</p>
</li>
<li>
<p>
Follow along:<br />
<a href="https://twitter.com/httpx_org">@httpx_org</a>
</p>
</li>
<li>
<p>
The origin story:<br />
<a href="https://rylan.io/tag/zero">Distilling the Web to Zero</a>
</p>
</li>
</ul>
<h3 id="known-implementations">Known Implementations</h3>
<p>Below is a running list of known guest languages.</p>
<table>
<thead>
<tr>
<th>Language</th>
<th>Project</th>
</tr>
</thead>
<tbody>
<tr>
<td>C#</td>
<td><a href="https://github.com/xui/xero">github.com/xui/xero</a></td>
</tr>
</tbody>
</table>
</body>
</html>