 Thanks for coming to my talk, everyone. My name is Andy Wingo, and this is a bit of a pallet cleanser of a talk in a way. I know there's been a lot of discussion of some very low-level details of all the parts of our systems, but ultimately when we work on these systems, we're doing them for some purpose that has a relationship with the user. So it's interesting to close the loop and see what is it that users have actually been doing with the things that we build. And if we look at our phones, like it's just apps, apps, apps, apps, apps, apps, apps, apps, apps, apps, apps, apps, right? And this is a talk about apps, and specifically you talk about good apps. You know, good user experiences. How is it that these things are produced these days? What is the space that we as open source practitioners can occupy, especially when making cross-platform apps? And about the end of the kind of long 1990s of development. We'll get to that in a minute. So if we think about apps, obviously everyone knows all of the things that are on their phone. And with such an established, quite commercial ecosystem, you would think that we actually know how to make apps, but I'm not so sure. And if we take a look at Signal, for example, which is an app that probably most of us have on our phones, and you look inside it, I was talking with someone, Yong Hei from Future Away the other day, and he broke down how it is that Signal is constructed. So open source, you can take a look at it. It has a cross-platform LibSignal layer, which ties together a lot of the core functionality, notably the cryptography and everything else. But then when it comes to the actual devices, we have piles and piles of code for Android, and a same-size pile of code for iOS. On the desktop, it runs on Electron, which is this, as you probably know, a UI toolkit built on top of Chromium itself, a big pile of code. And then on the server, of course, it has some code as well. This is a lot of duplication of work, at least on the mobile side. And one wonders, could we do this a different way? Is there a space for some cross-platform toolkit there? And, I mean, there is a space. There's a lot of people that are trying here, and there's a lot of people that are using a lot of different technologies to make apps. And this is just what fits on the slide, but the list goes on and on and on and on and on. Which, given that the list is so long, it actually makes me wonder, you know, do we actually have a state of the art, or is this just a transitory, non-equilibrium position where everything is in motion? So, I recently had caused... I'm a compiler's developer, myself. I'm not a UI developer or an app developer, but I recently had caused to look into this space, and I found some interesting things. And in the spirit of the humility coming before the opinions in the IMHO phrase, I wanted to take a look at what's out there, assume that it's built like it is for a reason, try to understand those reasons, see if I can condense them, and see what application they might have and what implication they would have for the future. So, in the first part of this talk, I'm going to go through what I learned, looking at the state of mobile apps and our frameworks, and then we'll use these to attempt to predict the future, or at least to attempt to orient our understanding of the present. So, in this investigation, I came up with five lessons. This is what I have. And the first one, like some of you, I see some of you are my age or older. I came up of age in a world of like GTK, right? We're always packing H boxes with widgets. This was how you built UIs back in the day, and it's an imperative paradigm. Here I have some pseudocode, taken from an excellent blog from Rafflainius, a Rust UI hacker, among many other things. And if you look, this was the state of the art in the past. You create containers, you imperatively add widgets to them, and when the state of your eye needs to change, you mutate that state. So, for example, you would have been bold here, imperatively adding your text widget to the stack, and then you attach a callback onto your button that when it is clicked, it updates the state, which in the case of this pseudocode is the count variable, and additionally, it updates the UI to correspond to the state. Note that with this paradigm, you have no guarantee whatsoever that the state and UI have any correspondence whatsoever. If they do, that's great, and if they don't, that's the usual state in which you have some sort of bug, some sort of view model mismatch. But around 10 years ago, and I don't expect folks in this room are JS developers, but this declarative, reactive UI paradigm came along and especially publicized by the React framework. So, here is a function which describes a UI, and the idea is you say what the UI is, and the framework takes care of actually making it happen. In this case, our UI is a function, and in these declarative UIs, UI is always a function of state or of input. In this case, the hello function receives an object, and if people haven't worked with JavaScript recently, this is a destructuring bind in which the name property on the incoming object gets bound to the name identifier, which we can then use inside this embedded XML, HTML, dialect that React also provided to JavaScript, which we'll discuss a little bit later. And this is one early example, but now there are just many derivatives of it, and you can see this that declarative UI as a paradigm is really one on the web. It's what people use, some declination of that. But interestingly, it also won on Android, right? I don't know if people have written apps with Jetpack Compose, but this is like the new way to write apps on Android as well. This is Kotlin code with a decorator, the composable decorator around your UI as a function, the message card being the function which creates the UI containing a text element saying hi, right, basically. And in this case, the UI is not returned from the function, but it's implicitly built up as the function runs. It's kind of a detail. We'll get back to that one a little bit later. And declarative UI also won on iOS, right? People don't write UIs really in Objective-C using UIKit anymore, they write them using SwiftUI. This is the main way that people write applications. It's an interesting data point to see that Apple has chosen this paradigm as well. I find Swift as the language particularly lovely, but here you see the body is effectively a function that takes a block parameter. And again, it builds up UI when you call the function. And if you go cross-platform, declarative UI is when there as well. Flutter is effectively one of the two main cross-platform UI toolkits, and it as well presents an interface which is UI is a function, function of state. And Flutter itself takes care of actually making sure that the UI corresponds to the state. So it's interesting to see that even when people abstract away from the platform, when they don't have to apply this declarative paradigm, which has been selected for you by Google in the form of Jetpack Compose, or by Apple in the form of SwiftUI, they still choose it. So that's an interesting data point. You might ask yourself why, and I have to admit that I think it's a managerial reason, right? You have your UI, it has parts, you can put a different part of your org chart on each of these parts, and because the framework makes it so that the parts interact in only limited and composable ways, that you can compose your UI out of your decomposed software developer team. It's a Conway's law affine developer paradigm. But also it does have some benefits, right? You make sure that the view corresponds to the state by construction. You don't have the problem where maybe you could be displaying a different count, but your count state is something different. But we must also say that it's not just the managers, it seems that UI developers also are pretty happy with this paradigm too. So just some observations. Lesson one, I take, is that declarative UI as a paradigm for making interfaces as one. And I find this interesting. The second interesting lesson that I find relates to the role of the paradigm, role of the framework when building apps, right? The developer, the app developer says what, and the framework says how, right? Because ultimately you do have some sort of imperative objects underneath, whether it be GPU or native widgets, depending on what you render to. And the framework is the one that does this translation between the developer saying what and the screen displaying what it needs to display. But if you think about it, if UI is a function of state, and if creating a new UI or a new presentation to correspond to a new state requires calling this function and getting back your whole UI, like constructing a whole new tree of UI objects, this is an ON problem. And this is not something you want to do on every frame or every state transition. So it seems like a bit of a performance disaster. And that's one of my other lessons here is that frameworks can enable ways of making apps. It's also a limit to performance, hopefully a lower limit, because it provides some degree of performance, but also an upper one in the sense that it is a layer on top of what the underlying system provides. In a way it's a risky bargain, right? You're giving up control when you lose a framework, when you use a framework to the framework author to ensure the good user experience that you're ultimately wanting. But I've seen that if I look at these, I find four main techniques that these frameworks use to mitigate these problems. And the first one is managed state. So if we look at the original example of our vertical stack in which we're adding a counter and we added the callback and all, this is what it would look like in React. I don't ask that you understand this because I mean React, gosh. It's just a weird world and you have to really dive into it and drink the Kool-Aid. But the upshot is that the state of the app is not, it's a framework concern. So you yield control as an app developer of the core model that you're working with to the framework. In this case, we're saying make me a new state object. If I call counter, if I use count, if I get its value, then I'm gonna get the value of count. And if I update it, I'm going to tell you by calling this function. And this allows the framework to build a dependency graph between parts of state and parts of UI. And hopefully, ideally, only update those parts of the UI that would change when your state changes. So the framework takes upon its responsibility saying I know which part of the UI is gonna change when the state changes and it takes care of that. And this is a technique used in all of the declarative UI paradigms. The second technique that I have, I see, is attempting to render incrementally. So if you, at every frame, at every frame when something changes, you get back the whole UI and then you translate this down to imperative objects fresh each time, that's again an ON problem. What you really wanna do is identify the parts of the UI of change and then mutate those parts of the underlying imperative library to correspond. And on the web, you're rendering to the DOM, right? The tree of HTML objects. You might be rendering to the GPU, actually. Your primitives might be textures onto which you are composing shapes, which have been test-lated and render via a variety of shaders. This is the Flutter story. Or you might render to native widgets, which is the React native story. It translates this kind of UI, a tree of UI objects to a tree of native objects. In any case, you wanna limit the number of operations on this target, effectively, what your system is compiling to. And so all of these systems use incremental rendering techniques in which you don't simply build the UI from scratch each time and replace it out. You identify what is the minimum set of changes needed so that I can modify the previous frame, the thing that I rendered the previous time, into the new frame. So it's a compiler. It translates functions to imperative actions. Additionally, we have some concurrency, but it's limited. It's difficult to effectively use all of the cores to improve performance. The most basic idea and the one that has the most success is in which you build the UI on one thread and then you handle the layout and the lowering of it to whatever you're rendering on another thread. In that way, you can half, more or less, this time. But it's not a panacea. This is hard to do on the web, which is why we don't see it very much in the declarative frameworks that target the web or web views. But it's easier on mobile when you have better cross thread communications abilities. And if you look at Flutter, for example, Flutter on its performance documentations, you'll see build and display frames in 16 milliseconds because they're targeting 60 frames a second rendering. And because they break down building the UI and then rendering it to the GPU, they say you have 16 milliseconds to build it, to build your new UI. And then you have 16 milliseconds also to render the frame. But obviously, this introduces a bit of latency. Your finger is moving on your app, and then your app is only changing two frames later, which is not the ideal experience. So they say if you can, actually do both of them in one frame. Build in 8 milliseconds and render in 8 milliseconds because that gives you the lowest latency. And additionally, this prepares you for 120 frames per second displays, which are coming as well. So again, concurrency, not a panacea. You need fast rendering below in any case. And then finally, if you treat the UI as a function, which produces a new tree of objects, you're creating many objects. You might be using reference counting. Some systems do this. Most of these use garbage collection for whatever reason. But garbage collection can introduce big pauses, as we all know. And the main technique that we see being used is concurrent garbage collection. The part of garbage collection that depends on the size of the live object graph is tracing. And so the technique called concurrent collection moves tracing off the main thread, so it shouldn't block the frame counters. It should install a frame. And then all you have to do on the main thread is just some synchronization and housekeeping. Ideally, it doesn't always work this way, but this is the technique that should reduce this kind of long pole pauses. And by that, I mean, when you fold up a tent, you can't get it smaller than the longest pole. So GC is often that kind of long pole in rendering. We want to reduce that. So in summary for lesson two here, that providing good performance is a framework concern. It is intimately bound up in the construction of the framework and the minimums that it can provide, but also the maximums. Frameworks can push app developers into good patterns, but they can also limit performance as well. So that's lesson two that I see. As a compilers person, if I see any problem, I'm like, how can I make this a compilers problem? Because it's more interesting that way, obviously, to me. But also, this is an opportunity to translate high-level things to low-level things, to make them run faster somehow. And compilers in UI, it turns out that to get, to provide the modern app experience, compilers are at the heart of it, actually. And all this, I think, three ways here. One is this Jetpack Compose snippet, which is how you build apps on Android these days. This is written in Kotlin, so already newish language, right? And it has a decorator at the top, this composable decorator. I find this very funny, but the Jetpack Compose requires its own compiler because it actually extends the Kotlin compiler with a compiler plugin. And that is the way that you get this kind of declarative like UI inside the composable function. That as you call text, it's not just producing a value, which then goes away. It adds the UI element that it produces to the current context. And additionally, with this composable decorator, it associates any usage of state with the current UI nodes. So if you have some usage of state, managed state, like we discussed for React Native, in this build function, then Jetpack Compose attempts to make it so that the UI only updates this part of the state when that state changes. Which I found very interesting when I found this out. And it's not, excuse me, it's not simply Jetpack Compose that does this. Swift UI is a framework on top of Swift, the language, and they had to extend Swift as well, not with a compiler plugin because it's all kind of siloed in Apple's world, but they extended the language with this idea of a result builder, which effectively does the exact same thing as Jetpack Compose does. It's a way to make parts of functions look more declarative. On Huawei's Harmony OS also, they took this paradigm as well with their Arc UI toolkit, which is based on JavaScript, but a dialect of JavaScript using extended compiler to provide exactly the same declarative UI experience. And additionally for React, which we saw in the very first bit where we were returning a segment of XML or HTML, that's also a compiler extension to embed effectively XML or HTML inside JavaScript. So I find it very interesting that to make good apps, somehow multiple organizations have decided that, okay, we need to work on a compiler first. And the lesson I take from this is that if you are going to make good apps, you need to control your compiler and be able to extend it to do interesting, expressive things. The second component of compilers that I see is, I've been mentioning JavaScript a lot, I think, in this, and JavaScript is usually deployed, is deployed in the form of source code, which then is parsed and compiled, and then compiled at higher optimization tiers. But for an app, that's not what you want, especially on a low-end device, right? You start the app and it doesn't start for another 10 seconds. So there's a trend for applying ahead-of-time compilation instead of just-in-time compilation. And for me as a compiler, a virtual machine person, this is odd because JIT compilers are what get you maximum performance, because you can specialize code, not just on what you know at compile time, but also based on what you see at runtime, a kind of online profile guide optimization, which you can specialize even to data coming across the network. But that's not what you want. You don't want peak performance on these devices. You want predictable performance and low startup latency. And so for that reason, there's a lot of tooling invested in ahead-of-time compilation. React Native has their own JavaScript implementation, which does ahead-of-time compilation to bytecode, and they don't plan to do JIT as far as I understand. RQI's ArcTS is also ahead-of-time compilation of JavaScript with their extended language to bytecode and Dart as well. When Flutter targets devices, they also do ahead-of-time compilation and no runtime JIT compilation, which is very interesting to see, given that the Dart team came from that kind of dynamic language JIT is max performance world. So, predictability over performance. And the third example I have is in graphics. If you go to Flutter and their performance pages, you will see this one, which is they have a whole page with a URL that even fits in the space provided about startup jank. And specifically, startup jank as relates to shader compilation. Flutter's backend is historically derived from the web by taking lots of things from the web and throwing them out and seeing what's left and good to keep. But, and they ended up on this architecture that uses Skiya, just like Google Chrome does. It's a backend drawing library. And Skiya, when targeting the GPU, tends to make shaders at runtime. And when you make shaders at runtime, you have to compile them at runtime to whatever the GPU takes. And when you have to compile them at runtime, then you can introduce some UI jank. And this could be some seconds, ultimately. So this is a problem that they've identified. And their solution is, of course, a compiler. Which is to compile the shaders ahead of time. But specifically to compile, to create a number of lower level primitive shaders that operate, there's only about 30 of them. It's fixed. And instead of creating custom shader objects at runtime, you tessellate the larger shapes to the smaller primitives and rely on these pre-compiled shaders. And this is currently in rollout. This is a recent development. It's not on Android yet, for example. It's only on the iOS. And I think only as an experiment. And interestingly, to keep a bit of cross-platform development, they still write all of their shaders in GL shading language. I can't remember which dialect, but you can look on the web page and find. And they have a compiler stool chain. Which centers around Spear-V, which then compiles to Metal and pre-compiles to the Metal code on iOS. And this can target WebGPU as well, as well as the OpenGL ES that's on even low-end Android devices. So compilers everywhere. Pilers are back. We're back, right? And interestingly, programming languages are also back. Which I find also very funny as a scheme person at heart. And so I'm always on the margins. And it's always nice to see more activity in the programming language sense. And maybe 10 years ago, you would get the impression that we were at the end of history, right? Like Java had won. And that's how you make apps for Android. And or if you're making for iOS, you're writing them in Objective-C, which is also a language from the 90s. And history is done. We have achieved the optimum UI construction paradigm and it is 1997, right? But that's not the case anymore. We have lots of activity now around language design, language experimentation. Notably for Android, people write apps in Kotlin. It's not Java. And on iOS, it's Swift. And talking about Flutter, it has its own language Dart. It's very interesting. We see all of these declarative UI experiments, which are ultimately semantic extensions to languages. And the idea from the long 90s was that all languages are essentially the same. Why use a different language? Because it's not going to provide you anything. But the idea from 2023 is that, actually, we can make better languages if our language, we can make better UIs if our languages are better. We can make better apps if our languages are better. And I find this to be a very interesting, an interesting thing is happening. Also, it's kind of revenge of the types for which I have positive and negative feelings, but it's a thing. We have to notice it. Swift has a very expressive type system. And people appreciate the ability to make these type-level assertions about the properties of programs that compile time and have them translate down to runtime behavior as well. And I would mention that it's not just academic noodling about program semantics. There is a real platform language UI co-design happening. And the example that I would have is that, initially, Dart was more dynamic than it is. But they found that to produce good code ahead of time, given that ahead of time is their new compile paradigm because of the mobile app startup latency story, that they needed to have stronger types so that they could resolve more things at compile time. So, for example, they went null-safe. The language no longer has null as a default value for any type. And additionally, their type system is what is called sound, which essentially means that the compiler can rely on the type judgments at compile time. There are no escape hatches at runtime. And in doing this, it's able to emit much smaller, more efficient binaries. So this is an interesting shift relative to 10, 15 years ago when the hottest research was around dynamic language runtimes and just-in-time compilation. We're looking more at ahead-of-time technology again. So, compilers, frameworks, declarative UI, that kind of the summary lesson here is that everything is open, right? We're not in a situation where there is one winner, and especially not a cross-platform winner. And so, if you're signal and you're looking to develop an app, I mean, in a way, it's understandable that you develop it once for Android and once for iOS, because certainly the cross-platform app development story is not settled right now. There's no consensus winner. And even the relative winners, which are React Native, which is kind of a port of React, but instead it targets native widgets on these platforms instead of the web, it is also churning on its own rendering system. It has this new one called Fabric. And Flutter is churning on its random system. It has this new one called Impeler. And in the JavaScript space, for anyone who's doing work on JavaScript, it's kind of synonymous with churn, right? Like, every year there's a new framework and a new winner and a new way that you must do things. And in the case of Flutter, I don't know if it's clear from the implications. So Flutter renders to essentially GPU objects, layers, textures, there's some composition and shaders. So what that means is that it does not use any text renderer built into the platform. Its text rendering is pixel perfect from iOS to Android, but that doesn't necessarily correspond to how text renders natively on either of those platforms, which is kind of a weird situation. I'm, as an older person, I'm reminded of when we shifted from ex fonts rendering on the server to rendering on the client, which we all kind of thought the world would fall down, but it turned out to be okay. I don't know if it's gonna be okay in this case. And then additionally, if you look around, things are weird. The fact that you need a compiler plugin to implement the default way to build an app on Android these days is weird. Like, that should be part of the language. Where is the kind of semantic definition? Why is it like this add-on thing? It's because it was an acquisition by Google. It wouldn't be like, you wouldn't build it like this from scratch. In RTS, which is this Huawei system, which is very similar based on JavaScript, they extended the TypeScript compiler, which itself is implemented in TypeScript, with their own language, which isn't well-defined either, which is also a bit awkward. And in React Native, they have their own JavaScript implementation with its own set of bugs and own set of unimplemented things. And if you want to use proper JavaScript, you need to run something ahead of time that translates proper JavaScript into the set that React Native can understand. And so in the end, I'm left with this idea that, you know, it's still open. There's space for something else, even, if that's what we wanted to build. And in that regard, we should look ahead and see, like, what should we do? Given these lessons, if we were to apply them today, maybe I'd try to apply them to the case of Signal, what do we do? Well, if we're starting a new app, the answer is more or less clear, right? Because Flutter is more or less the state of the art. Like, it produces good user experiences with fast rendering. It's got embedded tool chains, and it's a pretty good default solution. There are some reasons you might not want to use it. A difference between texts and native gestures, a difference relative to whatever the native platform uses, if that's important to you, and that might be something you think about, the fact that it's still changing, in a way. Like, it hasn't reached that, you know, optimal point, as indicated by the new rendering architecture impeller. And then also, it's a Google product, right? So, how many years is left in Flutter? I don't know. But it's something to keep in mind. Like, if you're gonna attach yourself to the Google train, yeah, you have to own that choice. And if we look at Signal, probably rewriting it in Flutter is probably not the thing, but I don't know. But noticing that there is this all existing cross-platform Rust library, then the obvious conclusion would be to replace both of these app stacks with something written in Rust. And using the lessons that we've looked at, then we would expect it to be then declarative, right? And indeed, there are a number of declarative UI paradigms on the way in Rust. It's also not a settled situation. This one is a crate called Dioxys, which you can go and visit this website to find. It looks like React. It's modeled off of React. It has the same good parts and the same mistakes, you know? But it's interesting. Currently, it renders to web views. The state of UI development in Rust is a bit weird. A lot of UI toolkits render to web views, relying on an embedded web browser to actually make the view. But I think this is a transitional state. And Dioxys has an experimental web GPU back in, web GPU not necessarily being linked to the browser. Like it's just a cross-platform low-level GPU library. But there are other options in the Rust world. Again, you have to put your state of your application into this. You have to give it to the framework to Dioxys to manage like React does. But it should work. It's definitely a story that involves a lot of ahead-of-time compilation. Rust Compiler is well-known to be excellent and to provide predictable ahead-of-time performance. I like that in Rust, you can use these procedural macros, this RSX macro here, to extend the compiler on the framework level to allow it to have this declarative UI subset inside it. This is this RSX bang operator. It's a procedural macro, I believe. It enables lightweight experimentation. You don't have to actually fork the language. And if you think about like, what would it look like to be flutter on Rust? That sounds great, right, you know? If I had all the euros, I would buy. So if we're not using Rust, like if we can think of another option, like, well, we have to choose a language, right? And there it's not clear. You could choose Swift, but it's kind of aptly. A safe choice in many ways would be to choose JavaScript, right? Because it's very popular, there's a lot of developers. You would attach yourself to the developer world. Just is very popular, there's lots of frameworks. Some of them render down to native widgets, which is the case of this one framework called native script, which you can look for, as well as React Native. You could render down to lower levels, but that doesn't exist yet in the JavaScript cross-platform world, but I think it could. And additionally, some of them provide some form of a head of time compilation to minimize startup latency. But I think there's still room here. It's not clear to me that there is a winner, even in the JavaScript space. But there are risks as well. If you decide to go all in on JavaScript, like, I mean, you're gonna be a small fish in a big sea, like the trends on the web, the fashion changes, you'd think they wouldn't matter, but they're real, you get developers saying your framework is great, and then in seven months, no, we've moved on. There are some risks, right? What I can only think of is abulience, or maybe fickleness, on the part of JavaScript. But I think that there are some interesting possibilities here, and notably most projects like this written in JavaScript are actually written in TypeScript, which compiles to JavaScript, and which does not have a sound type system. But it could, like you could imagine dialects of JavaScript with sound type systems. And I think that with a bit of foresight, if you're really doubling down on JavaScript as the application programming language, then you need to also think about what steps would it mean, what steps would it take to get from here to sound typing in TypeScript, and to be able to take advantage of a lot of these ahead of time optimizations that Dart gets. In that case, you get a Flutter with JS instead of Dart. And that sounds like a winning proposition as well. The other future that I can think of is that it's not to think about languages so much, but to think about primitives, like Flutter, okay, good, I think. What does Flutter need from a platform? Well, we could just build that, and then people can target that somehow. So you could think of virtualizing compute in the form of WebAssembly. WebAssembly is what I spend most of my time on, working on the implementations and compilers to WebAssembly these days. WebAssembly is the way you express compute, and WebGPU is the way you express graphics, which is this low level API, doesn't necessarily need to do with the web, and maybe some abstraction to get input, WebHID if we're talking about the web, and accessibility somehow, maybe ARIA is the web-ish platform for this. And then you start to get a bit weird because apps have different capabilities, right? Like you have the camera, you have sharing, you have nearby things. For me, this pitch is not so great for building apps, because once you start to think about the entire set of capabilities that you need to expose to WebAssembly, you end up with a pretty big runtime, and you also need a pretty big tool chain as well, because one app might need Bluetooth, and one app might not. And the app that needs Bluetooth needs to have it compiled in, and the app that doesn't should not have it compiled in, so that to the different app stores, it doesn't request that permission, right? You're not seen as needing all the permissions in your omnibus substrate for computation. But WebAssembly could have a place for extending apps, notably extending apps written in Rust, like it seems to be a good kind of plugin system for a rusty app. And the one thing that I would notice coming is that right now, WebAssembly is essentially a compilation target for C, C++, and Rust, and that's just based on how WebAssembly 1.0 is constructed. But there's something else coming, which is integrated garbage collection support for WebAssembly, integrated into WebAssembly itself, and that makes it interesting for Kotlin, Dart, and even, you know, Scheme and weird languages. So you could really open up space here for having GC in your WebAssembly. And in summary, okay, to the end of the talk here. Right now, we are in a period of the end of the end of history. There's lots of motion. There are some consensus positions, such as declarative UI, managed state, the attempt to get low startup latency, and the attempt to meet the frame clock. And in this space, in a cross-platform way, I would say the strongest contenders that are there are React Native and Flutter. And these are also very well-funded efforts as well. But it's not the end of the story. Like, we are not, again, at an end of history point. There's room for other things. And I've outlined what some things could be, and if I'm going to apply my predictions to a kind of crystal ball, I would say that we are probably going to see as an amateur effort, and possibly as a sponsored company effort, a kind of Flutter on Rust within a couple of years or so. Sound typing for TypeScript, much longer proposition. The JavaScript standards world does move, but it moves slowly. So if anyone wanted to work on this, I wouldn't think they would get success for some number of years more. And in summary, a lot of this investigation that I've done, I've written up a number of more detailed articles about each of the individual technologies that I've talked about. You can check the website if you want to learn more. But in the meantime, it's been interesting presenting this high level, what's on top of everything that we build. And the thing to keep in mind is this co-design aspect that the shape of the low-level things that we build needs to match the user experience. And so there's room for conversations that go all the way to how people are using these apps. Thank you very much. Thank you.