@abide/abide 0.57.1 → 0.58.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/package.json +1 -1
- package/src/lib/ui/compile/generateSSR.ts +9 -3
- package/src/lib/ui/dom/adoptRange.ts +27 -0
- package/src/lib/ui/dom/cloneStatic.ts +32 -1
- package/src/lib/ui/dom/markerDepthDelta.ts +3 -3
- package/src/lib/ui/dom/mountChild.ts +51 -16
- package/src/lib/ui/dom/mountRange.ts +14 -11
- package/src/lib/ui/finalizeStreamedChildren.ts +9 -10
- package/src/lib/ui/runtime/RANGE_MARKER.ts +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# abide
|
|
2
2
|
|
|
3
|
+
## 0.58.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 3941314: address every component mount as its own hydration boundary so a structural desync inside a child recovers at that one boundary instead of discarding the whole page (ADR-0049)
|
|
8
|
+
|
|
9
|
+
A component's range now ships as `<!--abide:c:PATH-->…<!--/abide:c:PATH-->` (the child's render-path) in place of the anonymous `<!--[-->…<!--]-->`. On a hydration desync inside the child — including a client-true / server-false `{#if}` gating an element's presence (`pending(...)`, `refreshing(...)`, any state fed by a client-only effect) — `mountChild` discards just that boundary and remounts the child fresh, rather than throwing out of hydrate and cold-rebuilding the entire page. Structural gating on client-asymmetric probes is therefore now safe (it degrades to a local remount). `cloneStatic` now verifies its claimed hydration run against the template's top-level shape, so a purely-static gated element throws (and recovers) instead of silently mis-claiming a diverged branch. No public API change; the SSR wire format for component mounts changes (server and client ship together and stay congruent).
|
|
10
|
+
|
|
3
11
|
## 0.57.1
|
|
4
12
|
|
|
5
13
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -616,16 +616,22 @@ export function generateSSR(
|
|
|
616
616
|
)
|
|
617
617
|
}
|
|
618
618
|
/* Non-hoistable child (off the top-level spine): await inline at its structural position,
|
|
619
|
-
sequential, as before — never streams.
|
|
619
|
+
sequential, as before — never streams. Bracket its html in the ADDRESSED boundary
|
|
620
|
+
(ADR-0049) `abide:c:CHILDPATH` in place of the anonymous `[`/`]` — CHILDPATH is the
|
|
621
|
+
child's render-path (`$$renderPath(ordinal)`), the same id the client's `mountChild`
|
|
622
|
+
composes — so a hydration desync inside the child recovers AT this boundary instead of
|
|
623
|
+
reaching the router and discarding the whole page. */
|
|
620
624
|
const result = nextVar('$child')
|
|
625
|
+
const childPath = nextVar('$cpath')
|
|
621
626
|
return (
|
|
622
627
|
anchor +
|
|
623
|
-
|
|
628
|
+
`const ${childPath} = $$renderPath(${ordinal});\n` +
|
|
629
|
+
`${target}.push('<!--abide:c:' + ${childPath} + '-->');\n` +
|
|
624
630
|
`const ${result} = await ${renderExpr};\n` +
|
|
625
631
|
`${target}.push(${result}.html);\n` +
|
|
626
632
|
`for (const $a of ${result}.awaits) { $awaits.push($a); }\n` +
|
|
627
633
|
`Object.assign($resume, ${result}.resume);\n` +
|
|
628
|
-
push(
|
|
634
|
+
`${target}.push('<!--/abide:c:' + ${childPath} + '-->');\n`
|
|
629
635
|
)
|
|
630
636
|
}
|
|
631
637
|
if (node.kind === 'element' && node.tag === OUTLET_TAG) {
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { scope } from '../runtime/scope.ts'
|
|
2
|
+
import type { UiProps } from '../runtime/types/UiProps.ts'
|
|
3
|
+
import { disposeRange } from './disposeRange.ts'
|
|
4
|
+
import { openMarker } from './openMarker.ts'
|
|
5
|
+
import { withScope } from './withScope.ts'
|
|
6
|
+
|
|
7
|
+
/*
|
|
8
|
+
Adopts a component's server range IN PLACE during hydration, given its already-claimed
|
|
9
|
+
`start` marker and the `closeData` of the marker that ends the range. Establishes the
|
|
10
|
+
child's lexical scope + render pass (`withScope`), builds claiming the existing nodes,
|
|
11
|
+
then claims the end marker the build's content stops before — the hydrate half of
|
|
12
|
+
`mountRange`, factored out so `mountChild`'s ADDRESSED adopt (`abide:c:PATH` brackets,
|
|
13
|
+
ADR-0049) and `mountRange`'s anonymous `[`/`]` adopt share ONE implementation and can't
|
|
14
|
+
drift. A build throw is leak-safe: `withScope`/`scope` dispose the partial scope before
|
|
15
|
+
rethrowing, so the caller's `discardAndRebuild` recovery starts clean.
|
|
16
|
+
*/
|
|
17
|
+
export function adoptRange(
|
|
18
|
+
parent: Node,
|
|
19
|
+
build: (host: Node, props?: UiProps) => void,
|
|
20
|
+
props: UiProps | undefined,
|
|
21
|
+
start: Comment,
|
|
22
|
+
closeData: string,
|
|
23
|
+
): { start: Comment; end: Comment; dispose: () => void } {
|
|
24
|
+
const scoped = withScope(() => scope(() => build(parent, props)))
|
|
25
|
+
const end = openMarker(parent, closeData)
|
|
26
|
+
return { start, end, dispose: disposeRange(scoped, start, end) }
|
|
27
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { CURRENT_PATH } from '../runtime/CURRENT_PATH.ts'
|
|
1
2
|
import { claimRun } from '../runtime/claimRun.ts'
|
|
2
3
|
import { RENDER } from '../runtime/RENDER.ts'
|
|
4
|
+
import { reportHydrationDivergence } from '../runtime/reportHydrationDivergence.ts'
|
|
3
5
|
import { foreignWrapperTag } from './foreignWrapperTag.ts'
|
|
4
6
|
import { templateFor } from './templateFor.ts'
|
|
5
7
|
|
|
@@ -29,10 +31,39 @@ export function cloneStatic(parent: Node, html: string): void {
|
|
|
29
31
|
const source = wrapper === undefined ? template.content : (template.content.firstChild as Node)
|
|
30
32
|
const hydration = RENDER.hydration
|
|
31
33
|
if (hydration !== undefined) {
|
|
32
|
-
|
|
34
|
+
/* Claim the run AND verify its top-level shape. A static clone is byte-identical to the
|
|
35
|
+
server markup, so it can only DIVERGE when a client-asymmetric gate above it (a sync
|
|
36
|
+
`{#if}`/`{#switch}` whose condition differs SSR↔client — `{#if pending()}`) rendered a
|
|
37
|
+
different branch on the server, leaving the claim cursor on foreign nodes. Unverified,
|
|
38
|
+
`claimRun` would SILENTLY adopt them — wrong nodes wired, cursor desynced for every later
|
|
39
|
+
sibling; assert the node names instead and throw the standard structural desync (like
|
|
40
|
+
`claimExpected`) so an addressed component boundary (ADR-0049) recovers locally. */
|
|
41
|
+
const claimed: Node[] = []
|
|
42
|
+
claimRun(hydration, parent, source.childNodes.length, claimed)
|
|
43
|
+
assertStaticRun(claimed, source.childNodes)
|
|
33
44
|
return
|
|
34
45
|
}
|
|
35
46
|
for (const child of source.childNodes) {
|
|
36
47
|
parent.appendChild(child.cloneNode(true))
|
|
37
48
|
}
|
|
38
49
|
}
|
|
50
|
+
|
|
51
|
+
/* Throw a structural hydration desync when a claimed static run doesn't match its template's
|
|
52
|
+
top-level nodes (a short run, or a differing node at any position — an element vs the comment
|
|
53
|
+
marker of a diverged branch). Named on the `hydrate` channel first, then thrown so the nearest
|
|
54
|
+
addressed boundary can discard-and-rebuild. */
|
|
55
|
+
function assertStaticRun(claimed: Node[], expected: NodeListOf<ChildNode>): void {
|
|
56
|
+
const short = claimed.length !== expected.length
|
|
57
|
+
for (let index = 0; index < expected.length; index += 1) {
|
|
58
|
+
const want = (expected[index] as Node).nodeName
|
|
59
|
+
if (short || (claimed[index] as Node).nodeName !== want) {
|
|
60
|
+
reportHydrationDivergence('static structure desync', {
|
|
61
|
+
expected: want,
|
|
62
|
+
got: short ? '(missing)' : (claimed[index] as Node).nodeName,
|
|
63
|
+
})
|
|
64
|
+
throw new Error(
|
|
65
|
+
`[abide] hydration desync at ${CURRENT_PATH.current || '(root)'}: expected a static ${want} node here, but the server DOM had a different node — SSR markup and the client build disagree on structure at this position.`,
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -5,9 +5,9 @@ import { RANGE_CLOSE, RANGE_OPEN } from '../runtime/RANGE_MARKER.ts'
|
|
|
5
5
|
`+1` opening a range, `-1` closing one, `0` for any other comment. A control-flow
|
|
6
6
|
block's rendered content sits between an OPEN and CLOSE comment — `[`…`]` for each
|
|
7
7
|
rows / if / switch / slot ranges, named `abide:…`…`/abide:…` for await / try /
|
|
8
|
-
snippet / html; the skeleton's own anchor
|
|
9
|
-
`0`. Keeping the rule here means a new
|
|
10
|
-
scans can never disagree on what nests. */
|
|
8
|
+
snippet / html / component-mount (`abide:c:`, ADR-0049); the skeleton's own anchor
|
|
9
|
+
(`a`) sits OUTSIDE any range, so it scores `0`. Keeping the rule here means a new
|
|
10
|
+
marker family updates one place, so the two scans can never disagree on what nests. */
|
|
11
11
|
export function markerDepthDelta(data: string): number {
|
|
12
12
|
if (data === RANGE_CLOSE || data.startsWith('/abide:')) {
|
|
13
13
|
return -1
|
|
@@ -3,8 +3,10 @@ import { claimChild } from '../runtime/claimChild.ts'
|
|
|
3
3
|
import { OWNER } from '../runtime/OWNER.ts'
|
|
4
4
|
import { RANGE_OPEN } from '../runtime/RANGE_MARKER.ts'
|
|
5
5
|
import { RENDER } from '../runtime/RENDER.ts'
|
|
6
|
+
import { reportHydrationDivergence } from '../runtime/reportHydrationDivergence.ts'
|
|
6
7
|
import type { UiComponent } from '../runtime/types/UiComponent.ts'
|
|
7
8
|
import { withOptionalPath } from '../runtime/withOptionalPath.ts'
|
|
9
|
+
import { adoptRange } from './adoptRange.ts'
|
|
8
10
|
import { commentData } from './commentData.ts'
|
|
9
11
|
import { discardAndRebuild } from './discardAndRebuild.ts'
|
|
10
12
|
import { mountRange } from './mountRange.ts'
|
|
@@ -20,8 +22,12 @@ The server decides per render whether a HOISTABLE child (ADR-0039) inlined (fast
|
|
|
20
22
|
streamed (still-pending), and the client build can't know which — nor whether this child was
|
|
21
23
|
hoistable at all — so hydrate mode probes the cursor:
|
|
22
24
|
|
|
23
|
-
- `<!--
|
|
24
|
-
|
|
25
|
+
- `<!--abide:c:CHILDPATH-->` next → the server INLINED the child in its ADDRESSED boundary
|
|
26
|
+
(ADR-0049 — every non-streamed child); claim the boundary open, adopt the range in place,
|
|
27
|
+
claim the close. A structural desync INSIDE the child (a client-true / server-false `{#if}`
|
|
28
|
+
gating an element's presence) throws through the build — recover by discarding just this
|
|
29
|
+
boundary and remounting the child fresh (`discardAndRebuild`), so the desync costs one
|
|
30
|
+
component instead of reaching the router and discarding the whole page.
|
|
25
31
|
- `<!--abide:await:CHILDPATH-->` next → the server STREAMED it; `__abideSwap` has already
|
|
26
32
|
spliced the child's `<!--[-->…<!--]-->` between the boundary markers before the bundle
|
|
27
33
|
ran, so claim the boundary open, adopt the inner range in place, claim the boundary close.
|
|
@@ -46,27 +52,56 @@ export function mountChild(
|
|
|
46
52
|
ordinal?: number,
|
|
47
53
|
): void {
|
|
48
54
|
const run = <T>(build: () => T): T => withOptionalPath(ordinal, build)
|
|
49
|
-
|
|
50
|
-
|
|
55
|
+
/* Both boundary ids are the child's render-path, computed here exactly as the server wrote it
|
|
56
|
+
(`renderPath(ordinal)`), so SSR and client agree with no counter. */
|
|
57
|
+
const childPath = run(() => CURRENT_PATH.current)
|
|
58
|
+
const addressed = { open: `abide:c:${childPath}`, close: `/abide:c:${childPath}` }
|
|
59
|
+
/* Mount the child's range in its ADDRESSED boundary (ADR-0049) — the create path and the
|
|
60
|
+
inline-recovery rebuild, so a client-only mount is byte-congruent with the SSR markup and a
|
|
61
|
+
desync has a named close to discard to. */
|
|
62
|
+
const mountAddressed = (where: Node | null): { dispose: () => void } =>
|
|
63
|
+
run(() => mountRange(parent, factory.build, props, where, addressed))
|
|
64
|
+
/* Mount into an anonymous `[ … ]` range — the swapped-in inner range of a STREAMED child, whose
|
|
65
|
+
own address is the outer `abide:await` pair. */
|
|
66
|
+
const mountAnon = (where: Node | null): { dispose: () => void } =>
|
|
67
|
+
run(() => mountRange(parent, factory.build, props, where))
|
|
51
68
|
|
|
52
69
|
const hydration = RENDER.hydration
|
|
53
70
|
if (hydration === undefined) {
|
|
54
|
-
/* Call
|
|
55
|
-
short-circuit
|
|
56
|
-
const handle =
|
|
71
|
+
/* Call mountAddressed() first, THEN register its dispose — pushing the call result inline
|
|
72
|
+
would short-circuit (never mounting) when there is no owner. */
|
|
73
|
+
const handle = mountAddressed(before)
|
|
57
74
|
OWNER.current?.push(handle.dispose)
|
|
58
75
|
return
|
|
59
76
|
}
|
|
60
77
|
|
|
61
|
-
/* Probe the cursor without consuming it
|
|
62
|
-
|
|
78
|
+
/* Probe the cursor without consuming it, and classify what the server emitted here by the
|
|
79
|
+
marker data. */
|
|
63
80
|
const cursor = claimChild(hydration, parent)
|
|
64
|
-
const
|
|
81
|
+
const marker = cursor !== null ? commentData(cursor) : undefined
|
|
65
82
|
|
|
66
|
-
if (
|
|
67
|
-
/* INLINE — the
|
|
68
|
-
|
|
69
|
-
|
|
83
|
+
if (marker !== `abide:await:${childPath}`) {
|
|
84
|
+
/* INLINE (ADR-0049) — claim the addressed open, adopt the child's range in place. A
|
|
85
|
+
structural desync inside the child throws through the build; recover by discarding just
|
|
86
|
+
THIS boundary and remounting fresh, so the desync costs one component, not the page. The
|
|
87
|
+
partial scope a throwing build stranded is already disposed (`withScope`/`scope`), so the
|
|
88
|
+
cold rebuild starts clean. */
|
|
89
|
+
const open = openMarker(parent, addressed.open)
|
|
90
|
+
try {
|
|
91
|
+
const handle = run(() =>
|
|
92
|
+
adoptRange(parent, factory.build, props, open, addressed.close),
|
|
93
|
+
)
|
|
94
|
+
OWNER.current?.push(handle.dispose)
|
|
95
|
+
} catch (error) {
|
|
96
|
+
reportHydrationDivergence('component boundary recovered — remounting', {
|
|
97
|
+
path: childPath,
|
|
98
|
+
error,
|
|
99
|
+
})
|
|
100
|
+
const handle = discardAndRebuild(hydration, parent, open, addressed.close, (after) =>
|
|
101
|
+
mountAddressed(after),
|
|
102
|
+
)
|
|
103
|
+
OWNER.current?.push(handle.dispose)
|
|
104
|
+
}
|
|
70
105
|
return
|
|
71
106
|
}
|
|
72
107
|
|
|
@@ -75,7 +110,7 @@ export function mountChild(
|
|
|
75
110
|
const inner = claimChild(hydration, parent)
|
|
76
111
|
const warm = inner !== null && commentData(inner) === RANGE_OPEN
|
|
77
112
|
if (warm) {
|
|
78
|
-
const handle =
|
|
113
|
+
const handle = mountAnon(before)
|
|
79
114
|
openMarker(parent, `/abide:await:${childPath}`)
|
|
80
115
|
OWNER.current?.push(handle.dispose)
|
|
81
116
|
return
|
|
@@ -88,7 +123,7 @@ export function mountChild(
|
|
|
88
123
|
parent,
|
|
89
124
|
open,
|
|
90
125
|
`/abide:await:${childPath}`,
|
|
91
|
-
(after) =>
|
|
126
|
+
(after) => mountAnon(after),
|
|
92
127
|
)
|
|
93
128
|
OWNER.current?.push(handle.dispose)
|
|
94
129
|
}
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { RANGE_CLOSE, RANGE_OPEN } from '../runtime/RANGE_MARKER.ts'
|
|
2
2
|
import { RENDER } from '../runtime/RENDER.ts'
|
|
3
|
-
import { scope } from '../runtime/scope.ts'
|
|
4
3
|
import type { UiProps } from '../runtime/types/UiProps.ts'
|
|
5
|
-
import {
|
|
4
|
+
import { adoptRange } from './adoptRange.ts'
|
|
6
5
|
import { fillRange } from './fillRange.ts'
|
|
7
6
|
import { openMarker } from './openMarker.ts'
|
|
8
|
-
import { withScope } from './withScope.ts'
|
|
9
7
|
|
|
10
8
|
/*
|
|
11
9
|
Mounts a nested child component as a marker-bounded range — the wrapper-free
|
|
@@ -22,6 +20,12 @@ marker — mirrors `mountSlot`/`when`). `before` (a skeleton-located node, the b
|
|
|
22
20
|
`anchorCursor`) places the range among static siblings on create; hydrate ignores it
|
|
23
21
|
(the claim cursor drives placement). Returns the markers + a disposer so the hot path
|
|
24
22
|
can rebuild in place.
|
|
23
|
+
|
|
24
|
+
`bracket` names the range's boundary markers. It defaults to the anonymous `[`/`]` a
|
|
25
|
+
control-flow re-fill and the swapped-in inner range of a streamed child use; `mountChild`
|
|
26
|
+
passes the ADDRESSED `abide:c:PATH` pair (ADR-0049) so a component's range carries its own
|
|
27
|
+
render-path — the same markers on create and SSR (congruent) and a named close a desync can
|
|
28
|
+
discard to.
|
|
25
29
|
*/
|
|
26
30
|
// @documentation plumbing
|
|
27
31
|
export function mountRange(
|
|
@@ -29,17 +33,16 @@ export function mountRange(
|
|
|
29
33
|
build: (host: Node, props?: UiProps) => void,
|
|
30
34
|
props: UiProps | undefined,
|
|
31
35
|
before: Node | null = null,
|
|
36
|
+
bracket: { open: string; close: string } = DEFAULT_BRACKET,
|
|
32
37
|
): { start: Comment; end: Comment; dispose: () => void } {
|
|
33
38
|
const hydration = RENDER.hydration
|
|
34
|
-
const start = openMarker(parent,
|
|
39
|
+
const start = openMarker(parent, bracket.open, before)
|
|
35
40
|
if (hydration === undefined) {
|
|
36
|
-
const end = openMarker(parent,
|
|
41
|
+
const end = openMarker(parent, bracket.close, before)
|
|
37
42
|
return fillRange(start, end, build, props)
|
|
38
43
|
}
|
|
39
|
-
/* Hydrate: adopt the server range in place.
|
|
40
|
-
|
|
41
|
-
claim the end marker the build's content stops before. */
|
|
42
|
-
const scoped = withScope(() => scope(() => build(parent, props)))
|
|
43
|
-
const end = openMarker(parent, RANGE_CLOSE)
|
|
44
|
-
return { start, end, dispose: disposeRange(scoped, start, end) }
|
|
44
|
+
/* Hydrate: adopt the server range in place, claiming `bracket.close` as the end marker. */
|
|
45
|
+
return adoptRange(parent, build, props, start, bracket.close)
|
|
45
46
|
}
|
|
47
|
+
|
|
48
|
+
const DEFAULT_BRACKET = { open: RANGE_OPEN, close: RANGE_CLOSE }
|
|
@@ -1,13 +1,7 @@
|
|
|
1
1
|
import type { FlightPromise } from './flight.ts'
|
|
2
|
-
import { RANGE_CLOSE, RANGE_OPEN } from './runtime/RANGE_MARKER.ts'
|
|
3
2
|
import type { ResumeEntry } from './runtime/RESUME.ts'
|
|
4
3
|
import type { SsrAwait, SsrRender } from './runtime/types/SsrRender.ts'
|
|
5
4
|
|
|
6
|
-
/* Comment-wrapped range markers — imported from the shared constant (not a local literal) so the
|
|
7
|
-
inline branch below can never drift from `generateSSR`'s own range emit. */
|
|
8
|
-
const OPEN = `<!--${RANGE_OPEN}-->`
|
|
9
|
-
const CLOSE = `<!--${RANGE_CLOSE}-->`
|
|
10
|
-
|
|
11
5
|
/* One hoistable child render staged by generateSSR: the reserved index in its output array, the
|
|
12
6
|
array itself, the child's render-path (the streamed boundary id), and its in-flight render. */
|
|
13
7
|
export type StagedChild = {
|
|
@@ -24,9 +18,9 @@ hoistable child was started as an isolated `$$flight` in the prefix and its outp
|
|
|
24
18
|
(an empty `$out` slot); here we fill that slot per child:
|
|
25
19
|
|
|
26
20
|
- A flight that has already SETTLED (a synchronous / warm-cache child, or a fast read) inlines its
|
|
27
|
-
html into the reserved slot
|
|
28
|
-
|
|
29
|
-
resume merge exactly as the inline path did.
|
|
21
|
+
html into the reserved slot in the child's addressed `abide:c:CHILDPATH` boundary (ADR-0049) —
|
|
22
|
+
the same shape `generateSSR`'s non-hoistable inline path emits, so an all-fast page adopts and
|
|
23
|
+
recovers uniformly. Its awaits / resume merge exactly as the inline path did.
|
|
30
24
|
- A flight settled REJECTED rethrows here — same as a rejecting inline await (500 before flush).
|
|
31
25
|
- A still-PENDING flight (genuine I/O) STREAMS: the slot gets an empty `abide:await:CHILDPATH`
|
|
32
26
|
boundary, and an html-only SsrAwait is pushed so `renderToStream` flushes the shell now and streams
|
|
@@ -53,7 +47,12 @@ export async function finalizeStreamedChildren(
|
|
|
53
47
|
for (const staged of slots) {
|
|
54
48
|
if (staged.flight.settled && staged.flight.error === undefined) {
|
|
55
49
|
const rendered = staged.flight.value as SsrRender
|
|
56
|
-
|
|
50
|
+
/* Inline the settled html in the child's ADDRESSED boundary (ADR-0049) — the same
|
|
51
|
+
`abide:c:CHILDPATH` shape `generateSSR`'s non-hoistable path emits, so the client's
|
|
52
|
+
`mountChild` adopts a hoistable-settled and a non-hoistable child identically (it can't
|
|
53
|
+
know which the server chose) and either recovers at this boundary on a desync. */
|
|
54
|
+
staged.out[staged.slot] =
|
|
55
|
+
`<!--abide:c:${staged.id}-->${rendered.html}<!--/abide:c:${staged.id}-->`
|
|
57
56
|
mergeChildRender(rendered, awaits, resume)
|
|
58
57
|
} else if (staged.flight.settled) {
|
|
59
58
|
throw staged.flight.error
|
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
|
|
8
8
|
A control-flow block's rendered content sits between an OPEN (`[`) and CLOSE (`]`)
|
|
9
9
|
comment; a snippet interpolation between `abide:snippet` / `/abide:snippet` (matching
|
|
10
|
-
`skeleton`'s `abide:` / `/abide:` named-boundary convention, like `OUTLET_MARKER`)
|
|
10
|
+
`skeleton`'s `abide:` / `/abide:` named-boundary convention, like `OUTLET_MARKER`); a
|
|
11
|
+
component mount between the ADDRESSED pair `abide:c:PATH` / `/abide:c:PATH` (PATH the
|
|
12
|
+
child's render-path — ADR-0049 — so a desync inside recovers at that one boundary).
|
|
11
13
|
The skeleton's own positioning anchor (`a`) sits OUTSIDE any such range. */
|
|
12
14
|
export const RANGE_OPEN = '['
|
|
13
15
|
export const RANGE_CLOSE = ']'
|