66// (createSlotProps); the document face (createDocumentSlotProps) predates
77// them. This spec pins what the document face does today, empirically:
88//
9- // - A NOT-READY arg (a thunk/getter throwing not-ready at the unwrap, or an
10- // eager call suspending in the component's render) is held COARSELY by the
11- // fragment model: the server component's own <Loading> defers the section
12- // and the retry delivers the settled value in the deferred fragment. This
13- // is the "holding" alternative DR-2 rejected for the stream face's
14- // granularity — but at t=0 it is functional and consistent with "markup is
15- // the snapshot" (generator-only-model.md §10). Pinned as PASSING.
9+ // Slots with args render as JSX (`<props.status …/>` — the compiled getter,
10+ // the only correct authored form); the one call-form test below pins the
11+ // WRONG form's failure mode on purpose.
12+ //
13+ // - A NOT-READY arg (a compiled getter throwing not-ready at the unwrap, or
14+ // the wrong-form eager call suspending in the component's render) is held
15+ // COARSELY by the fragment model: the server component's own <Loading>
16+ // defers the section and the retry delivers the settled value in the
17+ // deferred fragment. This is the "holding" alternative DR-2 rejected for
18+ // the stream face's granularity — but at t=0 it is functional and
19+ // consistent with "markup is the snapshot" (generator-only-model.md §10).
20+ // Pinned as PASSING.
1621//
1722// - An ASYNC VALUE PASSED WHOLE (the value tier: a promise/iterable arg)
1823// suspends at the inline read: the document face wraps it in a full
@@ -58,11 +63,18 @@ describe("document face × arg tiers (t=0)", () => {
5863 // invariance at t=0: the same authored crossing behaves identically
5964 // whether the mount is call-driven or the initial document.
6065 test ( "an async slot arg's inline read settles through the document's streaming (value tier)" , async ( ) => {
61- const ServerComp = ( props : any ) => (
62- < Loading fallback = { < span > GENFB</ span > } >
63- < section > { props . status ( { stats : wait ( 10 ) . then ( ( ) => ( { tokens : 42 } ) ) } ) } </ section >
64- </ Loading >
65- ) ;
66+ const ServerComp = ( props : any ) => {
67+ // Hoisted so the compiled getter reads ONE promise (a `.then()` inline
68+ // in the JSX would mint a fresh promise per getter read).
69+ const stats = wait ( 10 ) . then ( ( ) => ( { tokens : 42 } ) ) ;
70+ return (
71+ < Loading fallback = { < span > GENFB</ span > } >
72+ < section >
73+ < props . status stats = { stats } />
74+ </ section >
75+ </ Loading >
76+ ) ;
77+ } ;
6678 const Inline = frameTransformDirectResult ( ServerComp , { id : "dfa-value" } ) as any ;
6779 const html = await collect (
6880 ( ) => (
@@ -92,11 +104,19 @@ describe("document face × arg tiers (t=0)", () => {
92104 await wait ( 5 ) ;
93105 yield "second" ;
94106 }
95- const ServerComp = ( props : any ) => (
96- < Loading fallback = { < span > GENFB</ span > } >
97- < section > { props . status ( { tick : ticks ( ) } ) } </ section >
98- </ Loading >
99- ) ;
107+ const ServerComp = ( props : any ) => {
108+ // Hoisted: the tap's whole premise is ONE cursor with two consumers —
109+ // an inline `ticks()` in the JSX would mint a generator per getter
110+ // read and quietly dissolve the thing under test.
111+ const tick = ticks ( ) ;
112+ return (
113+ < Loading fallback = { < span > GENFB</ span > } >
114+ < section >
115+ < props . status tick = { tick } />
116+ </ section >
117+ </ Loading >
118+ ) ;
119+ } ;
100120 const Inline = frameTransformDirectResult ( ServerComp , { id : "dfa-iter" } ) as any ;
101121 const html = await collect (
102122 ( ) => (
@@ -114,12 +134,16 @@ describe("document face × arg tiers (t=0)", () => {
114134 expect ( html ) . toContain ( "second" ) ;
115135 } ) ;
116136
117- test ( "a not-ready thunk arg is held by the fragment model and delivers settled (coarse holding)" , async ( ) => {
137+ test ( "a not-ready getter arg is held by the fragment model and delivers settled (coarse holding)" , async ( ) => {
118138 const ServerComp = ( props : any ) => {
119139 const m = createMemo ( ( ) => wait ( 10 ) . then ( ( ) => "READY" ) ) ;
140+ // `v={m()}` compiles to a getter — the common authored form: the read
141+ // defers to the border, where it throws not-ready at the unwrap.
120142 return (
121143 < Loading fallback = { < span > GENFB</ span > } >
122- < section > { props . status ( { v : ( ) => m ( ) } ) } </ section >
144+ < section >
145+ < props . status v = { m ( ) } />
146+ </ section >
123147 </ Loading >
124148 ) ;
125149 } ;
@@ -141,9 +165,14 @@ describe("document face × arg tiers (t=0)", () => {
141165 expect ( visible ( html ) ) . toContain ( "v:READY" ) ;
142166 } ) ;
143167
144- test ( "a not-ready eager call arg suspends the component render and delivers settled (same coarse holding )" , async ( ) => {
168+ test ( "the WRONG form — an eager call arg — suspends the whole component render (pinned failure mode )" , async ( ) => {
145169 const ServerComp = ( props : any ) => {
146170 const m = createMemo ( ( ) => wait ( 10 ) . then ( ( ) => "READY" ) ) ;
171+ // The call form is the incorrect authored shape: `m()` is a top-level
172+ // read, evaluated in the component body before the border. JSX can't
173+ // even express this — which is the point. Pinned so the failure mode
174+ // is a known quantity: the not-ready read suspends the whole section
175+ // (no crash, no orphan), same coarse holding as the getter case.
147176 return (
148177 < Loading fallback = { < span > GENFB</ span > } >
149178 < section > { props . status ( { v : m ( ) } ) } </ section >
0 commit comments