Back to Index

FastHTML advapp.py walkthru - - Answer.AI dev chat #6


Transcript

OK, hello. I'm Jeremy from Answer.ai. And Hamel, who as of today-- also, where are you from, Hamel? Hi. Where am I from? I'm from Answer.ai. Where are you from as of today? Oh, Answer.ai, yes. Oh, there you go. Nice. Yeah. And since you just started today, I am going to help you understand first HTML.

Is that the plan? That's great. OK. And specifically, we're going to look at the idiomatic CRUD app, advapp.py. What's special about this app is that it's really designed to go through everything you might come across in a normal CRUD database app. So if you understand every line of code here, then nothing should be too mysterious for this style of application.

So maybe let's start by running it. So when you run a fast HTML application, you just run Python and the name of the application. And then it says link. And it pops up a link. So you can click on that. And so we've got a username and password. And so we've got here a application where I can add a to-do.

I can edit the to-do. You can view it. You can see it's using markdown. You can mark it done. You can drag it to be a different priority. You can delete it. You can log out. OK, so that's the app, right? So it covers quite a lot of behavior.

So I recommend, ideally, Cloud projects are the best for this, creating a Cloud project in which you grab the llms-ctx.txt file and dump it into this project. You don't have to remember that. It's here, linked to it. And you can basically ask anything at that point. So can you give me a basic skeleton fastHTML app to get started with?

That's a bit more than a skeleton, but that's cool. Yeah, so that can make life a lot easier. And one of the inputs to that context file is actually the application we're looking at. OK, so we follow the same pattern that, actually, the fastHTML standard library itself does for UI toolkits, which is just to import star.

That's actually what the official Python docs do for the Python TK module. And we've been pretty careful to make sure that only the stuff you actually need are going to appear. You can also import everything manually if you really wanted to. So there they all are. OK, so a to-do list needs to store the to-do somewhere.

We use SQLite and-- What is compareDigest? We haven't got to that bit yet. So I'll show you when we get there, if that's OK. So for SQLite, we do most things using this tiny little library called fastLite, which is a thin wrapper over really most of the good work.

This is done by Simon Willison, who created something called SQLiteUtils. But basically, it kind of makes your database look a lot like a dictionary. So there's a few different ways to use it. Actually, there's a way I kind of prefer nowadays, which people might find a bit less weird.

So maybe we'll use that. So fastHTMLExample is a good place to see lots of examples, as is gallery.fastHT.ml. Yeah, so-- and there's a to-dos here, as well. I kind of prefer this one. So basically, anyway, either way, you're going to have to say, this is where my database lives.

So here it is. Here's where my database lives. And then-- so users contains name and password, and to-dos contains ID, title, done, name, details, parity. Cool. So by default-- so basically, you say, OK, this is the definition of my user table. This is the definition of my to-do table.

By default, the primary key is assumed to be called ID. So if you want something else, you just have to say-- does that make sense so far? OK, user-- OK, so ID is always the primary key, unless you say otherwise. User table doesn't have an ID field. That's right, so it says PK equals name.

OK, I see. All right, so to basically get started, you need a fastHTML object. There's two ways to create it. One is with fast_app. One is called fastHTML. It can be quite helpful to just look at the code to see the difference. So basically, fast_app, all it does is-- this is actually fastHTML.

So it calls fastHTML, just passes everything across to it, adds static routes, and returns. So they're basically the same thing. So we're going to use fastHTML. And all right, so-- yes? All right, I don't know if I followed the fastHTML versus fast_app. Yep, basically the same thing. fast_app's just three lines of code that calls fastHTML.

All it does is-- Why would you want to use fast_app? Because you want static routes, which you normally do. So if you want a .txt, or a favicon.ico, or whatever, you want your application to respond by serving up those files, or GIFs, or JPEGs. So basically, all web apps, pretty much, you want to serve up those files.

OK, but you're using-- in this example, you're not using fast_app, using this? Yeah, so we kind of try to show everything manually for this example, just to show people-- Oh, I see, OK. --everything, the kind of-- yeah, otherwise, we wouldn't get people to show-- have a reason to show people how to do static routes, for example.

I see. We're doing things a bit more manually. So yeah, that's reason one. Reason two is it kind of gets the route for you. It saves two lines of code. I'm pretty lazy. Yeah. OK, so one thing that happens, then, in our to-do list is, if I try to go to slash, I'm not logged in.

It needs to redirect to login. So you can basically specify that some code runs before every request. In this case, I want to run some code to make sure that they're logged in, and if they're not, to redirect to login. So code that runs before every request is called before_where.

And so that's a class. And the thing you pass to before_where is one or more functions to call before your request runs. So here's our function. So it runs before the request runs. And so before_where and basically every other type of handler in FastHTML, you just write what you want to get access to in these parentheses here.

And FastHTML gives it to you automatically, just kind of similar to how FastAPI works, although FastHTML does a lot more of these than FastAPI does. So there's a session provided for free. The session is just a signed cookie, basically, with a dictionary in it. So we want to see if somebody's logged in.

So we're going to be saving whether they're logged in or not into a auth key in the session. So we basically say, OK, are they logged in? And if they're not, then we return redirect to slash login. Does that make sense? Yeah. Cool. Otherwise, this happens, which we'll leave for now.

OK, so that's how come we can see the login screen. Do you say just to ignore the todos.extra right now? Yeah. The styling comes from Pico CSS. You can use any styling you want. But this is a nice, lightweight, simple one that kind of works pretty well. And it comes in by default.

All you have to do is say headers. So this is the stuff in the head section of your HTML. And picolink is just a predefined variable, which brings in Pico. And the nice thing is, because these are just normal HTML applications, you can always just use source and see it.

So you can use JavaScript. And so we saw some JavaScript like this, right? This is JavaScript. That JavaScript comes from this library called sortable.js, which does drag and drop. It's pretty cool. And so in this app, I'm showing two ways of doing JavaScript. One is by things that are predefined.

And one is by things that we define ourselves. So sortable.js, it's really kind of there as an example. So there's a fasthtmljs.py. And so if I search for sortable, this is the whole thing, right? This is all it took to add sortable.js to fastHTML. That's all the code. And it's just basically a case of just returning a script tag with imports sortable.

So it's really easy to add JavaScript. And so you can see that in the app. There's that code we just saw. And we have the same thing for markdown. But just to show how to do it yourself, we've done this one manually. So here you can see markdown.js is just a string of markdown.

You can also put it in an external file. And that's just a script as well. So these all end up in the head, like so. OK? Yep. OK. So first route we're going to add is login. So the easiest-- well, there's lots of different ways to do routes. And a lot of it's just whatever you prefer.

But app.route is one convenient way to do it. And I use it so often that I normally just give it a name. So rt. So this is app.route. And then you just say what path. And then here for the name, you say what HTTP verb. So this is going to be a get on slash login.

And we're going to need a form. So again, you can just see it. So we're going to need a form with an action, a method. So this is all just plain HTML, HTTP, whatever. So rather than having to write to a form, slash form, you just use a function with the exact same name, but with a capital letter to start with.

So here's my inputs. Here's my button. Here's my action. So this just maps directly to this. We use that type by default so I didn't have to type it in. And then the other-- >> You are using this-- there's different ways of defining the routes. And you're using this kind of-- >> I'm using rt.

>> rt, yeah. Sometimes I use the app, the other pattern, because I discovered that in cursor, it wants to anchor on the name of the function in a file. And it gets confused. And there's multiple functions in the same name. >> Yeah. Yeah, there's lots of different ways of doing it.

They're all fine. Different people have different preferences. Yeah, so actually, you could just do-- that would also do the same thing. By default, it adds both a get and a post method. >> Oh, I didn't know that. OK. >> Yeah. >> Interesting. >> There's something for everybody. Also, in this form, we want the title so that it appears in the tab correctly.

So that comes from the title. So this little function here just basically causes two things to happen. It causes the form to be returned. And it causes the title to be returned. And also, it causes an H1 with the title to be added. So that's all that is. And again, it's these things that just-- it's good just to look at them so you can just see how tiny they are.

>> I did look at the source code for that function today. >> Yeah, so there it is. Yeah, so it's just a tuple of the title, a main, H1. The reason we use a main with this class is because that's what Pico expects. >> Cool. >> So happy with login, Hamel?

>> Yeah, it looks good. >> OK, so after we log in, it's going to do a POST request to /logins. This one's a GET request. So here's the POST request. So when we do the POST request, it's going to be receiving a form with a name. >> The action is part of the form, right?

Because the indentation was a little bit-- >> This indentation, sorry, this indentation or-- >> OK, no, the one in the code. >> Oh, the code. Yeah. Yeah, exactly. >> I'm wondering, maybe I should-- I've just switched to VS Code from Vim because it'll be more familiar, and also because I've set up this thing called Blockman, which is nice.

So it highlights the current section. So you can see what goes-- >> Oh, is that a plug-in? >> Yeah, it's a really nice plug-in. And even when you're not selected it, you can still see the selected bits. It's great. It's very helpful for-- >> It works like by parentheses and stuff?

>> Yeah, it just works by parentheses, exactly. So for stuff like this, it's nice to be able to see. >> Is there anything else like that? >> In the world? There's lots of good things in the world. >> What's up for your VS Code plug-in? >> I don't know.

This will do for now. Blockman, yes. Really cool. Make you feel like a Lisp programmer. OK, so when I log in, those two fields are going to be passed to my post handler, which is here. And so we've added lots and lots of comments to all of these, so we won't go through them all today.

But you can see exactly what everything is and why it happens and so forth. So we saw that there's going to be a name and a password. You could just type in here name colon str comma password colon str. That's one approach. Although I'm showing the slightly trickier thing here, which is where you can create a class with those things in it.

And it just automatically creates an object of that class. Does that make sense? >> Yeah, any benefits to doing it this way? Is there any kind of-- >> The benefits will come when we get to some of the other stuff, to be honest. There's not a huge benefit here.

In fact, maybe we should just-- well, I don't know, whatever. Yeah, OK. So we want to see if this user exists. So in Fastlight, or-- >> Just to be clear, the thing that's submitting the POST request is sending a login type? >> It's not sending a login type. It's sending a form.

And the form contains a field called name and a field called PWD. So we can see that it's actually a good idea to look at these things happening. So this is the best way to understand what's going on, is open up your thing here, click Login. And then you'll see here's Login.

And you'll see it's got this form data. Does that make sense? And so the result was a 303c other, so it's a redirect. And the reason we got a redirect there is-- well, we'll see in a moment. So yeah, so it got posted to here. We got received a name and a PWD.

And because we requested a login object, it constructed it by passing in those parameters to their same named places. >> I see. >> So now we've hopefully got a login name. Hopefully we've got a login password. If they didn't fill it in, then obviously they can't log in. So we go back to the reader.

OK, so then we try to see whether the user exists. So I think it's quite helpful, or at least I find it quite nice to just have a data-- just have a little notebook I can fickle around with. So actually, we've got it here. So we can grab our tables, users, there's a name and a password.

So Fastlight basically lets you search by primary key by just using square bracket notation. So JPH exists, JPHK doesn't exist. Make sense? >> Wait, when you had square brackets without anything in it, you had some completion thing that showed all the fields or something? What was going on? Or was it just imagining that?

>> I think you're just imagining that. >> OK. >> There's a dot, and specifically dot C gives me the-- >> OK. Let's see. >> And T gives you the tables. >> That's awesome that it's available like that. Yeah, that's really good. >> Yeah. Yeah, yeah, it's super nice. I like it.

This is all in the Fastlight documentation. See how to do all that stuff. Yeah. So basically, I try-- actually, Jono recently added a slightly nicer way to do this, which is instead you can say if login.name not in users colon. You could do that way nowadays instead of using try except.

Anyway, so in this kind of simplified thing, the logic we have is just that if a user doesn't exist, there's no separate registration key screen. We just create them there and then. So that's how we create new users in the system. OK, so now we're up to your question from earlier, like what's compareDigest.

So at this point, we have a user. We just created one. And so one nice thing about Fastlight is all the things like insert and update and stuff always return the new object. So this has returned the user that's been inserted. So we want to check whether their password is correct.

You could just check with the equals equals to see if they're correct. But there's a technical security issue there, which is depending on the length of the password, for example, the amount of time that it takes to finish calling equals equals varies, which actually basically gives some information to an attacker about your password.

So this is a more secure way of comparing two passwords. That's all. So yeah, if the passwords are not the same, then again, return back to the login screen. So yeah, by this point, we've got a login. So we store it away in our session. Auth is a special session name.

It's a good place to put your login, just because basically you can then-- if you put auth as a parameter, you'll be passed-- VICTOR: What made you know about this Python security thing? Is it something you already knew about? Or is there some other thing that you recommend reading to know about different things like that?

Well, for FastMail, I had to know all that stuff. And then originally, I didn't bother with it in this application because I thought it's kind of confusing. But then somebody raised an issue saying you shouldn't teach people how to create insecure web apps. So I put it in. Is that fair enough?

OK, you were bullied into it. You know me. I'm easily led. OK. So if that was all successful, then now we can go ahead and redirect to Slash. And that's what you saw. Here, we've got a C other redirect. OK, so what happened when we went to Slash? When we went to Slash, we call this one, our get.

And just to show off, I wanted to say jph is to-do list. So remember I mentioned we can use auth as a special parameter name. So that gets it from the session. And the session's signed, so somebody can't pretend to be somebody else. So that's all that is. What is in auth?

All this is in comments. OK. Current username. Is it just-- yeah, what is auth? Is it-- It's a thing we stored in SysAuth. Oh, OK. On the other hand, when you click Logout, it's just href slash logout. So that's just going to call get on logout. So it just deletes auth from the session.

Takes you back to the login screen. So I mentioned earlier that, for example, you want to be able to see the Australian flag for the favicon. That's not going to work unless you have a static file handler to recognize. Like a .ico file should be just passed directly. And so this is a bit of a long way of doing it.

This is the most complete way of showing how to do a static file handler. So we're addling a root. And basically, this is all Starlet syntax. So if you look up Starlet, it'll teach you what this means. But this basically says, in the path, one of the bits is called the filename.

And then another bit we're going to call ext. Do you see they just get passed in here? And this, as it mentions in the notes here, is just any one of these extensions. So you can change that to other things. And so, yeah, and if you get that, we return a file response, which means the contents of the file.

If you use FastApp, you don't have to add this. It's just done automatically. I see. OK, that makes-- So yeah, here's our home page. So title. OK, so then along the top, we've got JPH's to-do list, logout. So I just used a grid, h1 title, logout. Like, here's grid, right?

It's just a div with class equals grid. Actually, maybe even easier. It's just to see it here, inspect. It's just a class equals grid. That's just how Pico does it. What was the other HTX thing? Basically-- FDHX, sorry. I don't know why I wrote it this weird way. It should basically just say div.

Like that. That's just a long form. Pretend it says that, because that's-- I don't know why I wrote it the weird way. Yeah, so you can see it's all just generating HTML, right? That's all. So then we've got this new to-do here with an Add button. So that's the input.

New to-do is the Add button. And to make them look nicely together like this, Pico has this group thing. Again, you can just see it. What's a group? It's just field set row equals group. So you have input. OK, it's just horizontally grouping it? Yeah, yeah, just you can read the Pico docs to see what Pico can do.

Yeah, basically, no one's going to write all of this CSS by hand unless they're crazy. So just pick a framework and stick to it for a while until you know it. And I would say just use Pico for a while, because it's fine. It's not amazing, but it's fine, which is exactly what you want, right?

Yeah. OK, so then we want to get a list of to-dos. So to get a list of to-dos, you just write to-dos, parenthesis, parenthesis, like so. So that's how you return the contents of your table. However, I need to make sure that I don't have players to-dos. I only want my to-dos.

So we have a really convenient thing in-- All right, just a follow up. The to-dos, is that the SQLite? This is Fastlite, yeah. That's the table or the Pathlite. OK, OK, OK, yeah. OK, I got it. So there's a few-- you can pass in a WHERE clause and arguments for that, ORDER BY, LIMIT, OFFSET, SELECT.

So it's just like a very, very simple one-table query that, by default, just returns the whole table. Because I'm kind of terrible at remembering all the security details, and I think everybody else is as well, we've tried to make that really pretty automatic. So this is where this comes in.

If you call this on your table, then it will add this criteria to all of your queries, both data manipulation and select. So to-dos.extra.jph-- sorry, you've got to say name equals jph. And you can see now it's only displaying my to-dos. So that's just automatic at that point. That's great, yeah.

I was needing this earlier. I didn't find it, but it makes sense. This app isn't multi-user, I should mention. That's stored as a global. So in practice, you would actually probably want to take this out and put it inside each method, each handler. But this is just an example.

OK, so now we want to order them by priority, which-- just write order by priority. So there you go. OK, ready 0, 1, 2, 3. So I find it much easier to write these kinds of things if you have a little notebook or REPL attached to the database you can play with.

So all of the fast HTML tags, we call them fast tags or FT, always take zero or more children. Oh, wait, that thing that was global, the extras, if you didn't want it there, would you have to add it to all the routes? Yeah, what would you-- OK, is there a way to not add it to all the routes, to somehow add it to all the routes at once without being global or whatever that you recommend?

You know what I'm trying to ask? I do know what you're trying to say. I'm going to have to think about it. Decorator would probably be your best option, I think. Hmm. Yeah. OK, yeah, so here's the list of to-dos. All tags, or FT, or fast tags as we call them, always take zero or more children as the positional arguments.

So that's why I use star here to say these are all of my children. So it looks really weird here that we're putting database objects as arguments to a tag. Like, what the hell does that mean? And the answer is that FastHTML automatically calls a special method called DunderFT on anything that's inside a tag.

And so we actually added a DunderFT to our to-do class. So this is what's called to automatically turn a to-do into HTML, basically. So when a to-do is displayed, it's displayed like so as a list item. So it returns a list item. And it contains a hyperlink with the title, a hyperlink with the word edit, optionally a tick if it's done.

And then we also add hidden fields for the priority and the ID. Yeah, so that's the list item. So this is-- - I'm just watching, but I'm not familiar with FastCore. And I want to explain what that patch is doing. - Yeah, so it just adds this method to this class.

Although, if you use the newer style approach I described, you can just add it directly to the class. So you don't have to use patch if you don't want to. - The what file approach, sorry? - The newer style approach that I showed you earlier. Where did we have that?

Let's do it. So you can just go dot, dot, dot, dot, dot, dot, dot, dot. Paste. So here, we want to add ThunderFT to that. So we could just move that and put it just straight inside the Judo, like so. You don't need that anymore. Yeah, either is fine.

Just another reason this is kind of nice approach. OK. So all right, so yeah, this is kind of cool that you just put your to-dos directly into your form. And then the form, we can give it an ID, a class. The form has a post, which we'll come back to why that's there.

- I have a question. When you're doing something like that to-do component, or whatever the list, the to-do list, or the thing, did you do that part in a notebook? And then copy and paste it? Or did you do it directly in the editor? And what is your approach?

- Honestly, there's so little to it, I think I probably just did it directly here, once you remove the comments. There's no logic in it. So generally, I actually only use notebooks and stuff for stuff where there's logic. There's no logic here. This is just HTML. But you certainly can, like if you-- - Like if you wanted to fiddle around with the way it looks, I guess, you just kind of hot reload and just-- - Yeah, so you can use fast tags directly in a notebook, and they'll render as that.

Or if you want to see what they're actually going to look like, you can just work a show on them, like that. But it does depend on having the style sheets available. I think the easiest way to really see what it's actually going to look like with the styling and stuff is just to run your app.

And we have live reloading, so as soon as you edit your app, you'll see the results. Yeah, again, lots of comments here explaining exactly what's going on with all that FTF whatever stuff. Yeah, so as before, we've got the title, and then we return that stuff. So I guess, yeah, what happens when you add a new one?

It's going to be clicking the Add button inside the form. It's going to post a slash. So here's the post to slash. And so this is kind of cool, because the form has a title, and we say we want to get a to-do passed to us. So it's going to construct a to-do for us and give it the title that's in the form.

So that's how we can have a to-do automatically. Can you talk a little bit about when to use async and when not to use async? Oh, I don't know why any of these say async. You don't need async. The only time you need async is if you ever find that you need to type the word, oh, wait, and if you ever type the word, oh, wait, and don't have async, Python will yell at you.

So don't type async unless Python yells at you. No, it's the short version. So yeah, unless you know you need async, you don't need async. I don't know why this is async. None of these are async. OK. Why does it-- you mean, it still works the same? I mean, it isn't doing-- Yeah, it still works the same.

Yeah, you don't need async. OK, I'll just forget that I saw it. Yeah. Which is hard for me to do sometimes. It works if it's there. It works if it's not there. So. OK, so we've got a to-do now. So we can insert it. And remember, I mentioned that when you insert something, it also returns the thing that was inserted.

And after it's been inserted, it's now got an ID added. This automatically gets an ID. And then the other thing that's going to happen when we-- so when I click Add, two things should happen. This to-do should appear in the list, and this input box should disappear, which is what happens.

So the reason why is because we return the new to-do. And that gets rendered with Dunder FT automatically. And we also return a new input box, which is empty. And it has the same ID. So it replaces the one that's already there. So that's-- >>So how does it know to-- I guess, how does it know to do the replacement?

Is that in-- >>That's what OOB means. OOB means out-of-bounds swap. It means to replace one that's already there, and it looks for something with the same ID. >>OK, and then the to-do's insert is just like putting it in that element and just pushing another-- >>No, this is inserting it into the to-do's table.

This is a fastlight. >>Oh, to-do's table. OK, sorry. >>And after-- and then it returns the inserted to-do. And then it gets automatically rendered as a list item using Dunder FT. >>Oh, OK. >>And it gets added to the end of the list because when we hear this button, when the button that we clicked has an HX swap after begin and target to-do list, so it sticks it at the end of the to-do list when it's received the new to-do.

So this is like-- I can't teach you all of HTMX. This is-- so this is just HTMX. It's not fastHTML. So you should definitely, definitely, definitely read the Hypermedia.Systems book, which is really, really, really good. >>Media.Systems book, OK. >>Yeah, this book is-- >>So now if I take some HTMX-- >>It's an absolute must-read.

>>Yeah, you read this book. Don't just do the HTMX blog post tutorials, whatever. >>No, no, no. This is a very good book. It's a really good book. There's nothing else like it. >>Hypermedia.Systems. And as per usual, build things as you read it, of course. >>Just to make sure I understand, the Hypermedia.Systems is related to HTMX?

>>This is the HTMX book, yeah. So part one describes web 1.0 applications. So it doesn't even mention HTMX. It's just using HTML and HTTP, because that's kind of like understanding that's a prerequisite. And then part two is HTMX. Part three, you could ignore it. It's about mobile. >>OK. >>Yeah, so once you've read that book, you know HTMX.

OK. >>So I understand this input-- sorry, I understand that this-- OK, it's going to get this thing back, this to-dos back. And it's going to swap it out. But then how does it-- this new input is being-- I know it says swap that one out, too. But then how does it-- is that a fast HTML thing?

>>No, none of this is fast HTML. This is all just HTMX. So it's best just to look at it happening. So just watch it. OK, so it did a post to Slash with this payload. >>OK. >>And the response it got was two things-- a list item. So that's the rendered version of the to-do.

And the empty input box with OOB. So this one, because it says OOB, is inserted to replace whatever has that ID, which is this. And this one is inserted wherever the requesting button asked it to be inserted, which was after the beginning of the to-do list. And this is the to-do list.

That's the to-do list. Anyway, this is all just HTMX. So we can't hope to explain all of HTMX here. Yeah, in fact, that's basically it. Like, there's an Edit screen. So when you click Edit, again, you can see what happens when you click Edit. It calls Edit Slash 0.

And it does that because our ThunderFT says so-- Slash Edit Slash 0. And so that's going to call this. And so this is-- again, this is Starlet. So Starlet says this is how you should say what special things in the path, variables in the path. And it's going to get stuck into your parameters.

So here's whatever was in the path. And we pop up our form with the title, and the save, and the done details. And this is the super nifty thing in FastHTML, which, as it says here, it populates the form. So it takes the form called res and populates it with the contents of todo's ID.

And so that's why you can see it's been populated with the todo. And so then you-- What's the hidden thing about this hidden element? Yeah, so you need to-- basically, all state has to be stored inside the form. Again, this is just like-- this isn't even HTML. This is just HTML.

So state has to be stored in the form. So it needs to know what to do with it. So this is todo 0. So that then when we click Save-- I see. It has to know it's todo 0. With this title, it's done. These details-- anyway, so yeah, so I think we should revisit this after you've read the hypermedia.systems book and feel comfortable with hypermedia applications, because that's kind of a starting point for this.

And this-- you can think of FastHTML as being an HTMX server almost, really. OK, I'll read it tomorrow. But I know you've already built stuff just by getting a cursor to help you. So you don't necessarily have to know all these details when you have a language model at your disposal.

Yeah, I like to know. Otherwise, I get lost. Yeah, otherwise, maintenance and debugging and all that gets confusing. Yeah, I'm still somewhat skeptical about the ability for people to actually run businesses on the back of code if they had a model create and they don't understand. Can't imagine that really works in real life.

It's considerably faster. Yeah, I think it's a little bit misleading. I think even when I'm using-- yeah, even though I'm using LLMs with FastHTML, I kind of know a little bit about where to look in a way. But if I didn't know anything at all, I think I would get stuck.

Like, I kind of need examples and stuff. And I don't know. Well, I hope you found that helpful. For people watching, you found that helpful. Yeah, it was really good. Thank you. I'm going to go and get tea. Have a good night. All right, thank you.