I'm suspicious of any type signature with a newline
Streamed
After a quick romp through issues and PRs, I was joined by a surprise guest (surprise even to me), Joel Drapper. We confirmed the dark mode bug is fixed (though, spoiler, it reopened the next day). Then we discussed SQL performance in sqlite and toured Joelβs Phlex and Literal gems comparing them against how Lobsters handles those topics.
scratch
topics
x PR review
hr style https://github.com/lobsters/lobsters/pull/1450
x dark mode https://github.com/lobsters/lobsters/issues/1445
x gvl instrumenting
story merging ui/ux
merge plan:
split the tree of comments by which merged story they're on w _listdetail header
on a merged story, no top-level comment box, one in a <detail> for each
refresh display at top of single-story page, clearer metadata
cross-link stories - footer w backref links to stories and comments linking here
cross-link comments - details/metadata w backref links to comments linking here
don't cross-link if source comment has a negative score or mod soft-deletes the link
refactor the db into headline and stories?
story submit: submitter can choose to merge into existing story (and is encouraged to when correct!)
refactor user suggestions into 'draft posts' rather than per-field tables
user suggestions can promote top-level comment with unsubmitted link to Story
user suggestions can merge and unmerge two stories
vague/misdesign: hidden stories or hidden links; same for save, upvote
write up a AboutController page explaining merging
title
the delphic oracle of editors
just make up a number, it's performance analysis, you're allowed
I'm suspicious of any type signature with a newline
post-stream
activejob enabled in prod? rm cron jobs from ansible
Transcripts are generated with whisperx, so they mistranscribe basically every username and technical term. They're OK but not great, advice appreciated.
Recording
01:47Happy Monday.
Sound off if you're here.
So this is Lobster's Office Hours.
Albynton Hello!
It was like 50-50 whether that was going to be Lobster's Office Hours or local host office hours when I clicked on the icon.
Ah, hey, Albenton.
Good to see you again.
pitrApen hello!
So Lobsters is this site, a news and social media forum about software and hardware development.
And, oh, hey again, Peter.
I'm curious.
So let me do my intro.
So office hours is the best time to ask any kind of questions about the site or the code base.
And then when people don't have questions, I hack on stuff.
So Peter, you opened an issue recently about, why is the font size so different?
The navigation.
pitrApen hadn't time to reply to your comment, sorry
And I left a longer comment in reply and we talked a little about it on stream even I think, but I don't know what next steps are here.
pitrApen (missed the stream)
haven't had time okay no worries I just wanted to make sure that you weren't I didn't go super deep on it on stream I didn't like write this whole big comment about this big comment I did not write that on stream so there isn't any more info there I just thought you and I might have chatted about it a little these kind of blur together for me sometimes I just wanted to make sure you weren't waiting on anything from me because
It sounded like you had big ideas there.
pitrApen how are you feeling towards change? are you concerned about response from community?
So looking at what's hanging out, Rahul has been busy this last weekend, and Colonel has started on one more big refactoring.
04:02That's a good question. Part of my response, I'm going to answer the second part first because it's easier. I am a little concerned about response from community because people can get pretty bothered when UI changes happen. So I tend to do them fairly slowly and only when it's really like, it's kind of like the pressure builds up then it has to get released with something that feels like it's really likely to improve the ui or improve community interaction and on saturday morning i deployed the prop shaft which is the new asset pipeline and it included a small bug in the css so a couple of the font If you are in dark mode some text changed from dark Gray. Well, from light Gray to white and if you were in light mode some change text changed from dark Gray to black. And it is one of the most reported bugs on the site, I think, four or five people said it in the chat room and then. It got added as a GitHub issue, and I got two DMs and an email about it, and then someone also submitted it to the site because they didn't check the bug tracker, and it got, I don't know, 50 or 60 upvotes and was pinned to the top of the page for the weekend. People care about their fonts. So that bug was kind of annoying, and I'm going to finish it on stream, I hope. But then also... It prompted someone to come and attack this HR again. Let me run that so by the time I get over here, it's done. I mean, it's a CSS change. It's not going to break the build, but I like seeing the checks. And the GitHub UI has gotten flaky. If you want to see something, it drives me up the wall. If you open the command line, oh, look, it closed them. So every time one of the build steps changes phase, it smashes this thing closed, it loses its state. I had a build running and I was trying to like copy text out of here for another PR this weekend. And like I got the text selected, and then it went away. Something has happened to GitHub in the last quarter. And a whole bunch of jankiness has snuck in and it drives me up the wall. Everything is slower. When I hit back, it like JavaScript happens it seems instead of loading. Anyway. So we kicked around this issue. So that's kind of my general take on changes is I want them to be worth prompting a whole bunch of frustration and concern because people definitely thought the font change was intentional and were bothered by it. I really hoped with all of the activity and so many people saying they wanted it to go back, that someone would help. But I ended up debugging pretty much the whole thing from an iPad, which doesn't have like dev tools. And so there's no inspect, there's no rapid iteration loop. So then to answer your first question.
08:14I'm pretty nervous about changing the navigation.
...26Some of these things feel like you are tuning the navigation to your personal preferences for how you browse the site. Some of them are like, especially the sub nav ones, the sub nav is kind of messy and there's a little bit of redundancy I don't want to get rid of, but then otherwise it's just kind of confused. And so if you had a coherent vision, especially one that made it visually clear it's a sub nav, I would be almost instantly on board with that. I did see that you worked through basically all of the routes, which was very similar to the work I did in 580, where I used rake routes to list every route and categorize them all.
09:29I hope that answers your question.
...37I guess the other thing, the only other thing I wanted to emphasize was this second paragraph in your issue that you opened, you seemed really oriented towards stories and links. And if that's the way you mostly use the site as a source of interesting things to read, that's great. but I'm guided by wanting to create discussions because that is a rare and precious thing. Aggregating links is much less ambitious than actually running a forum, I guess. So I'm kind of beating that one to death because I don't want you to spend a ton of time trying to make links way more prominent than if I'm not going to be interested in any of them because I don't see them leading to better discussions. So the other way to say that is if you phrase everything in terms of this will lead to more and better discussions, I mean, you can have anything then, right?
10:58Hey, CSS variable, no.
11:59pitrApen fair enough, thanks for talking about it here in detail
All right, good deal.
12:21I don't mind CSS style.
...30There we go, so if anybody doesn't see context on this pull request i'm doing a quick review on. If you have read the sites Andy Chu is Andy C on the site, and he is the person who uses HR all the time, so if you look at his comments, you will always see HR it's just a stylistic thing that he prefers. And I recently made those lighter because they were, were they unstyled? I don't recall. They were either unstyled or just very dark and divisive. So I copied the style from the vertical lines and then Andy was like, hey, and then he realized that actually I name checked him in the commit because it's him. I haven't actually tried to, Graph it but I bet that if I searched the database like 99% of the HR is on the site would be him. So I'm glad he jumped on that style issue. So then let's peek at. These that have gotten touched, even if they are all drafts that don't need to get done yet. And then is this ready? Oh, so long as I'm doing pull request review, let's grab this link. And off screen, I'm putting it in the scratch. And over the weekend, I merged two of Rahul's PRs. GitHub, please. I want the things they do, and they can't remember how I want things sorted. They added, really? All right. Huh. I didn't notice Rahul had closed that. I'll go see if he left a note. So I merged the sprockets to Propshaft, and I was genuinely amazed that it did not break the asset pipeline in prod. I actually went and did a... I checked the application CSS, and then I dumped the source to make sure it was up to date, because I was like, did I just not deploy it at all? Because it didn't even hiccup in the deploy pipeline, and then it all just worked. So that was really impressive out of Rails because it has been a little bit of a struggle with the ACID pipeline. We've also sort of minimized our use of it. Then the other thing from our pool that I merged was configuring ActiveJob with Solid Q. And that's going in the post stream. I don't think ActiveJob is enabled, so I've got to check ActiveJob.
15:56I think the cron jobs are still quiet. It's harmless because everything is just filling a cache or reifying. So if the cache gets filled twice, it doesn't matter. There was one minor tweak to the switch to Propshaft, where Rahul had missed that he needed to wrap JSON parse around something. Rahul dropped by the last stream, and he said he does, I believe, a lot of database work, and so it sounds like he's less familiar with frontend, which is probably why we also had some CSS break. Oh, I should talk about this one.
16:50So there was that change with the dark mode that broke. There was the little flag thing that broke. That's fine. I merged a typo fix from Klamath that broke the CSS again. So I'm going to debug it again because that's just in Chrome. The text is the wrong color again. So where's the... Why did you close this buddy.
17:26Well, I hope he comes back to it. And then this one is clean up towards. yeah there doesn't look like it's much changed okay so he changed some things since the stream so he popped up on the last stream and we talked through this one because he had some questions like why do you keep wanting to get rid of these strings okay so these are the suggestions i made and then And that's a typo. There's a fix. I really wonder what his editing setup is. Oh, this is nice. Using the counter cache feature. And he just copied my query over here. Clever. There's a There's a resets counter function, right?
18:47Yeah. I mean, it's fun to see my own code, but...
19:02Wait, is it just one record? What if I want to reset all of them?
...15Do you not? That's irksome. I understand that they want to write this for Oh, what if you called this and you had 100 million records in your database? You'd have an update that ran for 10 minutes and locked the database. But come on, how many databases have that? Can't you put unsafe or not for scale in the name and move on? Well, I guess I don't need that.
20:02All right, that's coming along. I believe he still has it marked as a draft. No. OK, so he's still working on it, though. And then he had one open question. If that didn't blow up, I'm going to just leave it as resolved.
...32Updated, so you got that one, yes. He got that one too. I saw that. We just saw that. Just saw that. This one he pointed out it was a. OK, so he did that. That was the new migration.
21:28AnakimLuke chaelcodes sent me here!
that's pretty minor and so when he does his testing i will respond there all right so then colonel who just joined the site today oh hey anakin welcome what did she send you here for was she like oh this guy likes to ramble on stream you can get him talking about anything
AnakimLuke :D
All right.
So that's, oh, that's so much nicer.
22:12Yeah, I might as well do that in Ruby. It's pretty cheap. And especially we cash the heck out of that.
...25So I'm working through pull requests to Lobsters.
I can actually just share the URL.
pushcx https://github.com/lobsters/lob…
I forget.
AnakimLuke she was helping me out with getting started with rails and contributing to gitlab. I asked for homework and she said to follow you :D
I don't always remember to share PR URLs.
So if you ever want me to paste a URL on stream, feel free to pipe up anybody.
Ah, well, yeah.
So this is office hours for Lobsters and you can follow that GitHub link to browse the whole code base because it's open source.
So that's very available to you.
pushcx https://github.com/lobsters/lob…
twitchtd good afternoon
AnakimLuke π
And if you would like some practice, if I can keep track of my tabs, I have for you a whole bunch of issues tagged good first issue where you don't have to know much about the site to contribute.
Ah, hey again, Twitch.
23:25So there's lots of stuff to do.
...33Why is this blue? How have I not seen something here? Why do people write these
...56This is one of those things GitHub makes a lot harder than it needs to be. Because they have this whole feature around assigning, and I can't turn it off, and I can't automate it. And so instead, it just becomes this chore of comments that clutter up the discussions. And if I had seen this more recently than four months, I would be responding to them and saying, hey, please, do this or that and instead instead we just get this clutter forever thanks github i was joking in the chat room that maybe somebody will start jjhub for jujutsu And then we'll get a software forge that works for about 10 years before the front end gets rewritten completely and react and becomes an unreliable mess again. I mean we've gotten. But 20 years out of github that's a pretty good run.
25:20Yeah, as long as we catch the hell out of this, fine.
...29Yeah. So this is a limit of active record expressiveness that we can't say we want to extract the year, right?
26:02And then they've gone straight to database-specific functions.
Yeah.
Frici π
AnakimLuke something I noticed is how gitlab has many more buttons and options than github
I'm really curious about Steven Margheim, who is the author of Acidic Job, Chaotic Job, some other stuff, has started on a new... Hey, Fritchie.
A new...
Ruby library for interacting with SQLite, where he's actually parsing to the SQLite AST.
...48So you might see a really interesting database library come along in six months or so. Depends on how much free time he has, right? it's an interesting observation. I don't know really much of anything about GitLab. I've barely used it, certainly not in years, so I can't compare the two. I'm only comparing GitHub against what it was months and years ago. Oh, yes. So this, this,
27:37Frici usable? LUL
twitchtd gitlab ui seems busier
Frici sorry sorry, I should behave for 5 mins at least after tuning in
AnakimLuke @twitchtd yep. it's also a bit slower
twitchtd nice thing about gitlab is that it's opensource
their styles i mean github just hides a lot of stuff behind this kind of mystery meat navigation like this is a menu you have to open or you have to where is it we'd have to go to a pull request but there's a lot of stuff that you just don't see unless you hover your mouse over it which is the modern version of mystery meat navigation it's called that because it was a old web design thing from
AnakimLuke ^^
like 1996 there was this fad where you would have your navigation buttons across the top and they would be like random thematic themes so items so if it was like a movie website like say the matrix i don't remember if it had this but just imagine the matrix because most people have seen it well then it would have like a button for a telephone and a button for a
I don't know a handgun what else are matrix a button for the nebuchadnezzar a button for neos face a button for.
Somebody sunglasses and each one of those would take you to a different part of the site and you just have to memorize what each one is some of them, you could hover and see them.
And somehow we realized really fast what a bad idea that was because it was so confusing and nobody could ever memorize it and.
Nor would they, the median visitor is visiting your page for the very first time, especially in 1996.
And so it lasted like a year or two, and then it got beaten down.
And then what, 15 years later, all of a sudden people were like, let's do that again, because React makes it so easy.
And now it's coming back.
and it drives me up the wall.
Oh, Slack is the worst offender.
I have to rant a little.
AnakimLuke WutFace
Frici it is... but its so big and complex at this point twitchtd ...its uncertain if its really all that much of a point. (but always nice to have a large forge as OSS at least)
AnakimLuke oh god
Slack, there is nowhere I can put my mouse on the screen in Slack that doesn't cause it to pop up various menus as I scroll and different messages move under the cursor.
It drives me nuts.
You know, on top of how slow and buggy Slack is at its basic chat features,
Just the fact that it constantly has mystery meat navigation appearing and disappearing drives me up the wall.
30:04So this one looks very close.
dzwdz oh hey recursive CTEs again
I'm not sure what's left here.
...29Oh, I get what's left.
Frici I haven't used slack in ages... we moved to discord for most people using slack, companies included. And some even moved to Matrix! cause they missed headaches I guess
it's his issue that he just filed we'll talk about that on stream yeah dz active record now has nicer support for or nice any support for recursive ctes and i do
You'll have to dig back into the stream archives, but I do something unholy to get comments out of the database in the correct order performantly.
And of course, it doesn't want to port nicely.
Rahul has noticed it doesn't want to port nicely over to Postgres.
And it's one of those fiddly little issues of confidence error uses a
a zero byte, and Postgres doesn't want it in strings apparently.
32:37chamlis_ does this mean vi is a mystery-meat editor?
I'm not going to beat it to death, but comments order path is just wildly complicated.
And I guess somewhere he ran into the comment where I was like, all of this code is a bad idea, but I can't actually, in MariaDB, it doesn't let me use arrays to express, to cleanly express what I'm trying to express.
So instead, there's this
bizarre string hacking thing going on.
Good call there, Shamless.
No, I would say that Vim is just a mystery.
You know, it's an enigma of an editor.
It's like the Delphic Oracle of editors where things just happen and you have no idea why.
That's going on the stream title list.
So this is moving along.
This is moving along.
Oh yeah.
I gave him this query.
It's funny.
Like when I see something and I'm like, oh, that's my code.
And then I'm like, wait, did I write this comment more?
It's the code more than anything else that catches me.
34:13You can't have unsigned big ints in Postgres? That's so wild. I mean, I'm not... Bigint is so big, I'm not especially worried about the practical implication. I'm just surprised.
...54Does it even mention sign?
35:11Yes, thank you Stack Overflow.
joeldrapper you canβt have unsigned anything in Postgres.
Frici its not in the standard
Oh, hey, Joel, I was just thinking about you.
Frici ah there you go
Because I was thinking about the conversation we had on blue sky this morning and whether I wanted to talk about it on stream.
joeldrapper And you canβt have tiny ints at all.
It's not strictly lobsters related, but it is interesting.
You can't have tiny ants.
Oh, well, there goes one of my favorite terrible hacks.
...49pushcx https://bsky.app/profile/joel.d…
So for anybody who doesn't know, Joel is an excellent Ruby developer who is available for hire and then has been working on a series of interesting Ruby libraries
And even though I can't talk in his discord because I haven't given Joel my phone number.
I really like flex and I showed a little bit of it on stream a couple months ago.
And it sounds like Joel is building towards a whole Ruby web framework.
And that's very interesting because.
I think we're at a really good time for Ruby to take the stuff we've learned from the last 20 years of Ruby and make clean slate implementations of things.
joeldrapper Haha, I need to see if I can override that. Itβs an anti spam feature.
So like Joel has a testing library called quick draw that feels a lot like taking good ideas from mini test and test unit and our spec and making a bunch of,
backwards incompatible changes for parallelization, performance, and the right amount of expressiveness, because RSpec is, yeah, I'm just hassling you, Joel.
I think RSpec is nice, but it comes with a whole lot of, because it comes with, like,
an enormous API, it's very easy to get yourself in trouble with things like RSpec and Factory Bot.
We use both on lobsters, but I have to express a lot of strong opinions or they kind of turn into a maintenance mess.
37:43Yeah.
...51And then, Joel, I think just before you connected, I was talking about how Stephen Margheim is working on some kind of SQLite library.
Is that intended to be an entire database access gem?
Is it like an ORM?
Is it a data mapper?
joeldrapper I may try to run your test suite in Quickdraw.
I'm really curious if you know more there, because I don't think there's a gem public yet.
Yeah.
I'm...
I'm convincible for flex and quickdraw on lobsters.
RSpec is excellent, but it's huge, and I constantly have to express opinions or stop myself from doing things particular ways.
So if quickdraw...
You know, I haven't tried to use it in anger because it's alpha software, but if it does all the things we want, that's really tempting.
And Flex, similarly, I've only played with it on a personal toy project, but I just updated that to, what is that, 2.0.0 RC1 a couple of days ago, and it was a real painless thing.
You almost got a pull request from me for dock improvements.
God, I could spend the whole stream just writing doc improvements for Flex.
But it's in a good place.
My only hesitation about adopting those things is that they're new, and I was just last stream writing up how I'm opinionated because Lobster's is a volunteer project with limited dev time and a long time horizon.
We hope to be running for decades.
One of the nice things about RSpec and Rails in general is, yes, they will almost certainly be available
two decades from now.
Quick draw, just by virtue of being new, is harder to see that way.
But it's promising.
So one of the things I said...
I can't see the replies if I'm not logged into Blue Sky.
Where did it go?
I can't see the...
I don't know how to find it.
So I'm limited by not being logged into blue sky in the streaming profile because otherwise, you know, you'll see my DMS and nonsense.
joeldrapper Fractaledmind and I are writing a new ORM for Yippee. Plume is our SQLite parser.
And then the you're writing a new ORM plume is okay.
So plume is the thing I've seen ORM.
Hmm.
joeldrapper it is public on github
Well, is it I'm getting nonsense.
41:34AnakimLuke oh hi @joeldrapper ur the guy in the screen :D
do you have a link joel yeah yeah so if i had a better hosting setup i would just pull you on the mic actually joel i have done that before so i did a pairing with
Jean Boussier, Byroot, as we were tracking down what looked like a memory leak in Lobsters when it was using YJET.
So if you want to...
I'd have to pause the stream for a second, but I could call you on Discord and we could talk directly.
It feels right now like you and I have quite a bit of lag, so... What's his handle?
FractalMind.
42:40Ah, there it is.
pushcx https://github.com/fractaledmin…
joeldrapper I could join in 5 minutes, sure.
So this is the thing I was talking about earlier with the parsing all of SQLite.
That could be kind of fun if we want to take a left turn from my rough plan.
joeldrapper I was trying to type on my phone, sorry.
Yeah, just give me, I'll use that five minutes to make sure I'm not leaving any pull requests unreviewed because I have a couple of tabs open.
Ah, phone typing, that's why you're behind.
Yeah.
I'm really, well, you know, I'll save it until you're on.
So let me, all right, so off screen, I have a personal browser I can bring up.
And then I will have to grab my earbuds.
So it's really funny because this is, as much as it's possible to talk about tradition in the context of
streaming which is only 10 or 15 years old i feel like a very non-traditional streamer because one of my checklist items for starting a stream is i close steam and i close discord am i how do i add a friend
44:11I blocked some ad in the Discord web interface and I guess they reuse CSS styles for the pop-ups on users and now the Discord UI is half broken for me because I blocked the ad. All right, so there's good. So this is where I was. What is this? Oh, index, sure.
45:24Frici @joeldrapper pretty sure you can override that by giving him any role in the discord server. Unless discord changed that recently. Any role in the server should override the verification requirements.
Ah, you can make a new rule called Phone Weirdo and put me in it.
Frici @joeldrapper that... was supposed to be a reply... here
That would be fair.
...42Well, or if you like the general tone on lobsters, you could just make me a moderator and probably sucker me into helping. That's what I do with the lobsters. There's that.
46:02Right, the CSS. Can I just... Will this please just behave and let me knock this out? So this is the... I'll leave that open because we'll come back to it. So this is the kind of error I was working with on Saturday when I was fixing the CSS. And... They're so misleading. What it's actually complaining about is the arity of the function doesn't quite match because the variable is evaluating to either a tuple of RGB or color when it expects the alternate. And it's not like there's a proper type checking happen. So I think what's actually happening is that this expression is invalid.
47:25No, it's not. Or it's doing a... All right, so let's look at our actual... This validator is real frustrating because it won't actually show you the source line for these things.
...45Especially on the iPad, it was like, oh, okay, so just count 123 lines down and maybe you'll find the issue.
...56Yeah, this is just wrong. So the value error is that on line 123, and it doesn't actually have that line of code. And 128. So it's just mad about every use of this. So color light is defined. Color FG is a light dark. Let's grab this whole line and validate it.
49:05joeldrapper @pushcx I made you mod on the Naming Things discord.
There we go.
So I got a parse error at least.
So I'm going to try and get Joel called in.
arh68 what is the distinction between like `51 51 51` and rgb(0, 0, 0) ? idk much CSS i guess
So we make a voice call on Discord, and then I make sure that I'm sharing the desktop audio.
...44joel can you hear me say something again i see the icon flashing but i think my earbuds are not connected properly. I've got a... No, I can just grab that in. All right. Where's my output devices? Because I need to hear. Oh, there you are, Joel. Yep. Hello. All right. So let me now make sure that the screen can hear you. Can you say something more?
50:40Frici welp there goes your audio
arh68 audio kind achoppy now hmmge
I accidentally closed the volume thing.
That's the workaround for choppy audio.
All right.
Joel, do you have headphones on?
Because I'm hearing some of my voice back.
Frici ok yeah your audio is fixed but we have no joel
Are you hearing my voice back?
Okay, so folks should hear Joel too now.
You sound pretty darn choppy, but I can hear you talking.
Frici ok we are getting joel, choppy but joel
All right, so has confirmed that we do hear you.
So they're hearing you the same as I am, where we're getting like one in five syllables.
51:36If you're running a Linux desktop, may I suggest opening the control in the background?
...48Joel and I are chatting. We should, could maybe have prepped this, but you know, there's a very big putting on a show in the barn attitude towards streaming. Lest anyone think I'm any kind of professional.
52:24We're going to try another medium here. Give me just a second as I run a bunch of stuff.
...54Frici that's the other issue with desktop audio, we get all your discord pings too HahaSweat
And then how do I start a call?
There we go.
Oh, my God.
Did I manage to set a noise gate or a compressor on the desktop audio?
Do you all just get nuked with a really loud ringer?
If so, I'm sorry.
Oh, I did.
Thank heavens.
All right.
Well, I've closed desktop.
Frici I have a compressor on my end so that equilized the ringer anyway
So Joel, are you audible?
Do you hear me?
53:44Signal doesn't give you the little... No, correct Joel, you are completely muted.
Frici working with streamers taught me, I do not trust streamers LUL
Let's see.
Can I figure out if it's your end or my end?
Frici no offence sailsBless
Yeah, it's your end, I think, Joel.
54:26You don't trust streamers?
Frici we have a very low sounding joel now
Are we like selling used cars?
Very low sounding, Joel.
Let's just try turning Joel up.
Can you say something more?
55:01Frici oh you are also very low now
Now I'm low?
What did I even click on?
Let's put that back, that back.
Oh, I see it too.
What did I misclick?
Test, test.
What in the hell?
Okay, I should be at normal volume again.
Signal must have tweaked it.
Frici Yes now you are normal again
And if you hear Joel, you got me beat, because I sure don't.
All right, so I'm back to normal.
Very exciting.
Look at bad CSS while listening to someone debug Linux audio.
Thrilling, right?
So here's the playback, and I don't even see the signal bouncing for Joel talking.
56:07Frici maybe it wasn't joel i was hearing it was you and I thought it was joel due to the very low sound.
Alright, Joel's going to drop for a sec.
And then... Hopefully when he reconnects it will be good.
Yeah, you're probably hearing me and not Joel, because OBS is set up to mimic my headphones, and so it should pick up what I pick up when I unmute that channel.
...49So it's just the divided by 100 that it's mad about, right?
...58No. Do I have a missing something here?
57:12arh68 can --base-fg-light be `51 51 51` ? w/o error
I can't do.
It helps if I type the comment.
...33There's some fiddly little syntax error I'm just not seeing. Or I wouldn't be getting this parse error. There's like an extra one of these. So we open 1, 2, 3, 2, 1, 2, 3, 3, 2, 1. Hmm.
58:25arh68 that note about variables not being statically checked is .. perhaps notable
arh68 semicolon missing ?+ on line 3
arh68 i can't recall if semicolons are autoinserted in css
Semicolon is missing on line three.
One, two.
No, it's not.
Like that's, oh, line three.
That's the issue.
Okay.
And then this part, can I put back this divide by a thousand or a hundred percent?
Yes.
And I can put now this whole statement should be fine, right?
Hello.
Hey, that looked very positive.
Can you hear me now?
Yes, you sound excellent.
Excellent, okay.
And I'm sure in a moment someone on chat will confirm that they can hear you.
Frici JOEL!
Yes.
AnakimLuke yep
It must be something to do with my Mac.
arh68 audio good SeemsGood
AnakimLuke can hear
We're probably making history that for once the Linux audio setup was correct and then the Mac was difficult.
That's so true, yeah.
Yeah, that's never the case.
That's never the case.
All right.
So there's some kind of fiddly CSS validation error, and the validator just gives a lot.
rather than a very clear, hey, you've messed up the arity to this function.
Because it's not like CSS has type checking involved or a signature, but it's definitely happened
under the hood because there's a difference between a tuple like ARH has been suggesting trying and the color that results so like if I make tuple is what is he suggesting that won't work then right and that's different you can't divide a variable by 100%
like that, you'd have to use calc, I think.
It could be calc.
Yeah.
Our color scheme leans into transparency a lot, and nobody has really tried to clean it up and pick colors.
And so we just kind of kicked things along.
So what am I wanting here then?
I'm just looking it up.
01:01:04Yeah. So what I really want is basically what this person wants, which is adding a... Yeah. So he has... Putting a variable in our GBA.
...23Yeah.
So this was the issue that happened over the weekend where... Yeah.
the CSS had gotten updated to give a hex color here.
And then RGBA was mad because the arity wasn't right.
Because if you pass a hex color, you are passing two arguments to it.
And then the validation error was just not helpful.
pushcx https://github.com/lobsters/lob…
If you haven't seen the, I'll throw this in the chat.
Can I see the diff?
Where's the diff where this was introduced?
Let me show you, I think it's probably better that I show you the diff, including the stuff that changed over the weekend.
Is that?
Yeah.
All right.
So we are actually in luck.
I just have to dig in my messages.
So a user Kevin C contributed light and dark mode a couple of years ago, and I sent them a message on Sunday or yesterday where I was like, Hey, check it out.
Look what we did to your beautiful code.
We wrecked it.
And.
they were like, oh, actually, I watched the stream.
So here is the diff URL for my message, because I generated it.
It took me forever to figure out how to generate.
So then I have to go to this, and then this, and then this.
All right.
So Kevin,
originally created a bunch of these tuples and then they are all paralleled were all paralleled by creating colors out of them somewhere below that i'm not remembering right yeah so he named them like palette versus naming them color because there's this type issue yeah and then
below he called, created more of these color variables and set them with RGBA.
Yeah.
With the, yeah.
And then the resulting thing is much shorter, but you know, not working.
I didn't actually realize that you could do this with variables.
So you can set variables to just be tuples.
pushcx https://github.com/lobsters/lob…
Yeah.
And then it's like splatting them in, I guess, in Ruby.
Yes.
And this was working for, I don't know, five years or so.
We don't touch the CSS a lot.
01:04:18It must be an arity problem there. That's my guess. So we've got RGB, and then they had a call to RGBA. So let me swap in the editor here. So if this became RGBA, so base FG light is a tuple.
...57Try this line alone.
01:05:05Validate it. Validator does not support dark mode. Sorry to keep dropping into and out of its stream, folks. Seeing a semicolon. What did I just do? Oh, I was partially done with an edit when you figured out where I was going with it. OK, so that says it's valid. Next thing to do would be to get a Chrome browser up so that I can actually reproduce this locally. So let's bring Chrome on stream.
...52so that is the color it's supposed to be and so if i revert the edit i just made or change things back to divided i still don't see the bug all right so it's not must be in the comment text
01:06:22right so that just changed to black and then we'll put the commas and it did not change back so what i really want to do is make this obvious because otherwise i'm going to edit this and not realize what i've done because i was hoping it was like defaulting to whatever the value was before, so that if I set a default, it would fall through when it's invalid? No. Yeah, not having a good repro has made this a lot harder than it needs to be. Let's swap this over so I can see the full line.
01:07:30Let's think a sec.
How do I get a good test case out of this?
Where do we use ColorFG?
Use it on body, text area.
OK, so these.
So if ColorFG is messed up, I should get orange text.
No, I'm not.
So now I am.
If I said color app, which is undefined, what would I get?
I would get black text.
All right, that's not helpful.
So then let's make the new color obnoxious instead.
So instead of being var base-fg-light, let's redefine base-fg-light to be 255.
AnakimLuke I'm new to lobsters. how is it like hackernews?
10, 10.
So lobsters forked off of hacker news a long time ago.
And all right, so we are seeing red in the text.
So if I change this back, now I see it everywhere.
So this apparently is valid syntax.
Like, the CSS validator liked it.
And if I do it, I see our custom color.
So we're not falling through to whatever the default black is.
But if I, like, break this⦠I mean, I broke it enough that the whole CSS didn't parse.
Let's say, like, minus⦠What's the divided by 100% meant to be doing?
opacity apparently but that can't be like it would be doing opacity if you had a comma but not if you had divided by oh i just look at me making extra tweaks so here so i've done something obviously invalid and then the color is falling back to the default of black right yeah
I think probably what happens when you do divided by is it takes the last value of that tuple based
FG light and it divides it by 100%.
So then you'd just have an RGB color.
That's possible.
I can't think what else it could even try to do in that scenario.
So if I grab you, I look at your computed color.
No, these values are the exact ones I typed.
Oh, but I'm dividing by a hundred percent.
So let's divide by, I don't know, 70%.
Yeah.
For me, it doesn't seem to be, I'm just testing.
Hey, look at you being right.
Oh, did it do it?
Yeah.
Look, you see it change or no, it added the opacity.
I was misreading and I thought the last 10 changed to 0.7, but it added a 0.7.
AnakimLuke oh no css NotLikeThis
So no, that became RGB.
How did I get...
I don't see how that works at all.
We called the RGB function, but with this division, we got back an RGBA.
You see that distinction?
What if I called RGBA here?
Still get an RGBA.
This feels more valid.
Let's make a...
Hold on, I've got to drag the browser off screen for a second because I know my bookmarks are going to come up.
Frici its a vaguely similar overall idea, a link aggregator and discussion board but differs a lot in culture.
In any case, the division here isn't valid.
What are you dividing?
Even if it did work, it's confusing.
Yeah.
And if you look at the...
I've got to keep switching browsers.
There was a...
But you can pass a percentage in as the A value.
01:12:12pushcx https://github.com/lobsters/lob…
So this is probably what reintroduced it, because we had a whole lot of reports that, before I merged this, folks thought it was good.
But we're actually editing a different property.
...49AnakimLuke @Frici how come? it seemd even more computer science-inclined than HN
Fritchie, thanks for answering while we're fighting CSS.
pushcx https://jcs.org/2012/06/13/hell…
Anna Kim, this is basically our founding document.
It's the guy who created the site wrote a blog post about why he did it.
So maybe that's interesting to you.
01:13:19AnakimLuke huh cool. I'll check it out
arh68 @AnakimLuke right but a lotta folks are active on both. a lotta links come up on both. it's like coke & pepsi idk
So if it's invalid, why am I getting red, especially with the 0.7 on the end, instead of getting default black?
And also the CSS validator is not mad at me about it?
It's almost as if it's like, it is valid, it's just not doing what you expect it to do.
...50Except it is, right? I wanted this and I got it.
01:14:00Yeah. So what state am I in here? The only thing I've changed is inserted that and changed a hundred to 70%. Let's swap that back.
...23And what's the rest of the diff? Oh, I changed RGB to RGBA. Even if I take those out, I was still seeing my red color, right? Yeah. And actually, you could see the distinction there. This is a stronger red. Yeah. And then I save. And it lightened just a touch. -huh. So this does actually work if we call the right function. I just don't understand what it's meant to be doing. Like what's being divided by 100%. If we go back to that big overall diff. Yeah. So this guy is... I guess the other option is it...
01:15:26Perhaps it thinks that the division is, like, by default a one division.
Oh, one divided by?
Yeah.
chamlis_ mdn says rgba is an alias for rgb, with rgb recommended
Because one divided by 100% would be... Oh, Shamless says, RGBA is an alias for RGB with RGB recommended.
Hmm.
Hmm.
...59chamlis_ apparently also you prefix the alpha with a divide sign, maybe that comes from there?
I couldn't actually tell you the purpose of it because I don't see it in the old one.
So much code got pushed around.
It's actually hard to find stuff because everything was both a palette and a color and then duplicated light and dark.
01:16:33pick one of these that has a little more entropy in the name. Yeah, so what they're trying to do is apply this opacity.
01:17:12chamlis_ so I think the divide reads as a comma? https://developer.mozilla.org/e…
All right, let's back up one second.
Oh, Shamless.
So Joel, you wouldn't know, but Shamless has this neat trick on streams where he can find tons of interesting and useful info very quickly.
Hey, look at that.
They even show a divide.
If the channel value is not explicitly specified, it defaults to 100%.
If included, the value is preceded by a slash.
So it is valid syntax.
The comma was invalid?
That's not what I would have predicted.
Is RGB different from RGBA, perhaps?
arh68 that's inside the paren though .. hmm is it the same on the other side i wnder
RGB only has...
Does MDN say it's... Yeah, MDN says it's the same function.
01:18:24arh68 "dividing colors" is a strange idea to me lol
Yeah, I think we're all just getting thrown because we're used to seeing, you know, C-style functions of parentheses with commas between arguments.
chamlis_ there must be some lenient parsing going on, I think I've always separated R G and B with commas
And so seeing the A, we keep trying to interpret it as algebra, but it's not.
It's what the CSS syntax is supposed to be.
And it specifically says the number, it says the number, not the percentage.
Does it have a demo in here?
Examples.
I bet its examples are not going to have the percent character.
Whenever I've used RGBA, I've passed three values, and the last value has been a float.
arh68 commas-are-whitespace is eyebrow-raising to me LUL
Yeah.
That division is so strange.
I've never seen that before.
Do you usually use some kind of preprocessor, like Tailwind SCSS?
No, I usually just use commas.
Well, you've been writing some invalid CSS, buddy.
Maybe.
It works, though.
Oh, look here.
Their example has our percent style right alongside.
That last one is how I do it.
So they clearly... Legacy.
Oh, Legacy.
Congrats, Joel.
You're old.
Yeah, I've been writing CSS for a long time.
Yeah, me too.
arh68 boomer syntax ouchies
this simplification with light dark was I saw it because someone wrote like the advent calendar of CSS and I was like, oh, neat.
It does that thing that we have terrible hacks for.
Okay.
chamlis_ css is now a lisp with sexps?
Okay, so now we're all on the same page.
Thank you, Shamless, for finding this, that there are two syntaxes, and us old people are used to the comma syntax, but the non-legacy syntax is a slash.
So I guess what's your variable?
Hold on.
Stop.
Stop.
Wait, wait.
Look.
Okay.
They don't call it slash.
They call it space-separated.
And somebody lined these things up.
Is it literally as simple as we removed a space?
So let's bring the browser back.
Don't need the panel.
No.
Right?
CSS usually isn't about significant white space, but they call it space separated.
Yeah.
We're in what is the value of base FG lightweight now?
Oh, it's right there.
Okay.
Yeah.
It's two lines between those.
Yeah.
No, we're using the new syntax.
We're not legacy.
Yeah, exactly.
Yeah.
Okay.
So that definitely, you saw the step down, right?
So if I change it to 20, it's fine.
It works.
If I get rid of the space, not the parent, the space still works.
All right.
Good.
A little bit of sanity and then.
If I just get rid of this, we should get a real gray.
Yeah.
AnakimLuke will it error out if you divide it by 0? π
And if I put this back to 100.
01:21:38Hmm.
...46We're getting a nice dark color. Is it? You know people are mostly concerned about I just blew away this tested it didn't I. See if I even have a working log into this now so every once in a while I am. refresh the database with a broad backup because I want to make sure that number one i'm testing with real data in local dev but then also I.
01:22:24have a working backup pipeline. And so if I'm doing regular restores out of it, then I know I have one. So let's log back in.
...37Hey, wait, aren't you?
Yeah, I think you wrote this slash
class method, didn't you?
Yeah.
Every time I do it, someone on stream and chat is like, what did you just, that's not valid.
And then I show them a terrible hack.
you know, it's not terrible.
It's actually super convenient because I do this in console like every day.
so, oh yeah, I stuffed my inbox.
let's go back.
It doesn't look valid though, does it?
And then you realize actually division is just an ethical in Ruby.
Yeah.
And it's really funny.
That's real data.
It's peaceful, but it was lucky.
mjiig Many years ago I learnt never to assume anything wasn't valid ruby syntax
So that's the thing with actually being logged in.
Oh, the reason I logged in, that's why I'm messing around with all this, is I wanted to edit the settings in forced dark mode.
01:23:42There we go. Why are these going so slow? So this looks... I look at dark mode so infrequently. Yeah, so now we have a 0.87, which is not the absolute white that people have been complaining about.
01:24:12Is there... Sounds like you fixed it. Yeah, I'm trying to wonder, does Chromium have a dark mode setting? Because if it has a dark mode setting, then I can use the system. No, it doesn't. And then I could pull a page out of archive.org, but it does not have a dark mode setting.
...56Does Linux have a dark mode setting?
01:25:05arh68 I'm thinking of the jackson-pollock-paintings as valid perl research
I'm pretty sure you could do this from the console, actually.
chamlis_ there's a paintbrush icon at the top of the styles tab
Pull a page out of archive.org, I will find you the console.
Sure.
Come on, Teran.
...23Let's see, what is today? The 27th, so we'll just jump back a couple of days to this timestamp, and I will just grab whatever the first story is. There's a paintbrush icon at the top of the styles tab. Mm-hmm. Aha. So yes. OK. We don't even need the console. All right. So we'll bring these two next to each other. And what we see right now in this text, computed, is 255 and then 0.87. And let's jump into, of course, it's about C-verse rust, the top thing. No comments. Oh, I was like, wait a minute. I clicked on it because it said 23. So we're seeing the thing where sometimes with archive.org, you kind of jump around in time. Yeah. Yeah. So let's inspect the P tag. That's not pure black. Well, I'm in light mode. Yeah, but even so. Didn't you say in light mode it was pure? 0.87. So we're seeing the same color now. Yeah. So why are we mad? And this looks properly tinted gray. And this looks properly tinted gray. One of the complaints was that this was turning bright white.
01:27:07I think you fixed it. I guess? People keep posting comments on that one story and messages in the chat room rather than adding to the issue tracker, and they're not saying things like, I'm using this browser. So I have an iPad, and I'm trying to see is the colors look fine on an iPad. If I change my mode to dark, there's no way you can see this, but All the metadata looks good. All right, great. This looks... All right, so we've learned something about CSS syntax. And we're gonna restore that.
01:28:06And close the issue until someone can post a screenshot, and repro steps. I'm checking live on my iPhone and the grays look correct to me.
...33So is the lobsters test suite, that's mostly RSpec? It's 100% our spec. We have no front-end tests. We only have 800 lines of JavaScript, and it's very simple, like when you click this button, Ajax this URL kind of stuff, rather than maintaining state on the client. Okay. I need to see if I can run them in QuickDrawer.
01:29:15Okay.
...46All right. Okay. So I think that's all of my, all of the issues I wanted to look at comment on. So when folks don't show up for office hours with a bunch of questions and stuff, I use it as time boxing on the, working on the code base because otherwise I get tempted to just work on it 10 hours a day, you know? Yeah. All right. So just this mention. he left another nice all right so rahul this guy has been he's new to rails but the last two months he's submitted like eight or ten pull requests they've been real nice
01:31:01What is this icon? That's a link to an issue. It's just telling you that the issue is open. Oh, okay. All right. If you put markdown checkboxes next to them, then when the issue is closed, the checkbox will be checked. Schmancy. all right so and then his update all right so i'm reading this out of order he wrote this comment before this issue which i saw first so that's why i had to sort it out in my head okay so this is just his personal checklist and i'm happy with all of it this all sounds great so let's just leave a quick encouragement
01:32:03That's all the issues and that's all the PRS.
It is cool.
So, on the stream, I have my little table of contents.
We talked to PR review.
We did the dark mode thing.
Oh, GVL instrumenting.
I think I showed this on the last stream.
This is real short, Joel, but you might get a kick out of it.
Did you see by route talking about, whether.
rails apps are io constrained i did yeah that was great yeah so he wrote a blue sky thread and he and someone whose name i don't remember they they started like here we are eric axel nielsen nielsen asked like hey i kind of ran the numbers and it doesn't quite make sense and then he was
like adding more specifications and John was adding some more and adding some more.
And I was just, I kind of popped in and I was like, do you know, we could just like measure prod.
You don't have to, like we could just instrument it.
Yeah, exactly.
Cause one of the nice things about lobsters is it just runs on a VPS and I have root to it and no one is going to scream if I bounce the site on a Tuesday to add some.
So, John submitted a pull request.
to log some stats about it all.
And then where are we down here?
After a day or so of running, I collected all these stats, and he came back.
Here, I could share this link in chat.
pushcx https://github.com/lobsters/lob…
And since I'm looking at chat, thanks very much, Shamless, for finding the paintbrush thing.
I'm always in the Firefox dev tools, so I didn't know.
So where did he put it in here?
He
pasted a gist somewhere and I thought it was linked to the issue, but he actually did measure it that his measurement was that Lobster spends 40% of its time on IO.
Really?
Yeah.
Interesting.
So that's why YJIT was still a significant performance improvement for us.
GeordiFouronnes Firefox has a color picker too :)
Are you, what database is Postgres?
It's MariaDB and that issue I was just going through is we'd like to move to Postgres.
It's been something we've kicked around for years, but with MariaDB just getting sold to a private equity firm that focuses on enterprise software support, it was like, how long is it going to be great for our tiny open source needs?
And PostgreSQL has just been marching steadily onwards the last 10 years of adding really neat features and maintaining their pace of development and everything.
So I'm...
Really impressed by it.
Thank you, Jordy.
Yeah, I'm mostly in Firefox, Jordy, but the issue has only been reported by folks in Chromium.
Have you looked at SQLite?
No.
Do you think we could actually...
I don't have any feel for how much scale you can get out of it.
Probably...
Probably in Rails, comfortably, no more than, I don't know.
Just make up a number.
It's performance improvement.
You're allowed, right?
So I think the most insert performance I've ever got is something like 100,000 inserts a second.
But that was using, like, bulk inserts and, like, in a benchmark environment.
I don't know what the lobsters traffic is, but I expect it's mostly read.
Oh.
Right?
Enormously.
Heavily.
Yeah.
You know the 99-10 rule?
Yeah.
Yeah.
So this rule about forums is actually, we are even a little more lopsided.
For us, it's more like 90.6 or something, and then 9, and then 0.4, because we have the whole invite system going.
And so it's harder for people to convert from being a lurker into some kind of contributor.
So on a daily basis, we see actually,
these are monthly what's your peak rpm or rps are you asking total or reads first rights reads best rights honestly we're probably
Don't have one write per second because... Oh, you're fine.
Yeah.
Yeah, you'll be fine on SQLite for decades.
Yeah, so you're running on one VPS?
No, we have two.
Two VPS.
Yeah, because, you know, both Ruby and a database are RAM-hungry.
It's at the bottom of the About page.
So it is the DigitalOcean SVCPU 8GB, and that's the same for the web server and the MariaDB, because we ran into some slow stuff, and I was like, well, I was busy with work at the time.
So that's four VCPUs?
Yeah.
We were...
I was busy with work and I wanted to just throw money at MariaDB and I clicked on the wrong one and I upgraded the web server.
And so then I went and I upgraded MariaDB and in DigitalOcean, maybe this isn't too shocking, but it's a trivial operation.
Like you just have to reset to add more RAM and CPU, but if you want to turn down the RAM and CPU, you have to tear down the box and recreate it.
Oh, gee.
Yeah.
So.
didn't mean for the web server to be as big and the database server is only big because we had some slow queries and i was like well i'll just make the query the whole database fit in ram and that'll solve it and it did so okay yeah every once in a while i'm like all right i'm just going to throw money at this problem because when it's 10 20 a month yeah
I'm going to try and convince you to move to SQLite because I think this application is absolutely perfect for it.
Hold on.
There's an issue you have to look at then because... You would have to move down to one server instead of two, but you can always have a backup server.
I'll save that $20.
Yeah, and you can always have a backup server ready to take over.
But your database queries will take a few microseconds instead of, I don't know how long.
What's MariaDB, like 3 milliseconds plus 20 milliseconds latency or whatever?
That's high for latency because these two things are in the same data center.
Okay.
But yes, a couple of milliseconds.
With SQLite, you could embrace M plus ones as well, which is really nice because it means that your code can be much cleaner.
Well, age is a
So I've moved us to use Persopite, which is very loud about 1 plus n queries.
So we almost don't have any.
Right.
But have you ever thought that, like, sometimes the optimization to remove an n plus 1 feels like it's not as clean?
Like... Well, yes, because... From the code, it feels better to think about it with, like...
pushcx https://github.com/lobsters/lob…
nesting where you have these nested queries.
And you don't have to join everything ahead of time.
Well, I'm going to say yes, but no.
So the reason I asked if your SQLite library was headed more of an ORM direction or a data mapper direction is I constantly hit my head on the fact that ActiveRecord is an ORM and doesn't support
whatever shaped query I drag stuff back as and so I just me personally for my sequel style I would like to be able to write queries that like join tables and I have one wider projection or I want to write a yeah oh a big one is we have I can't pull it up on stream because it's full of I mean the point of it is it's full of stuff but
We have these two tables, mod notes, and oh, they're next to each other, and moderations.
So moderations is the stuff that's in the public mod log, like I changed the title on a story.
And mod notes is an internal moderator only, like it's just a text area.
And we paste in stuff like I had a discussion with this user about X, or hey, I got bad vibes off of this guy.
And so the combination of the two is insanely valuable.
It's like, oh, I deleted two comments from this person where they were in an argument, and then I left a mod note explaining it.
But because they're two separate tables, I can't present them together in Active Record because I can't project them into a state where Active Record is happy to treat that as a virtual table or a class.
It just doesn't have features around that.
And so you're saying like, wouldn't it be nice if you didn't have to think about one plus ends and joins and it's, I want to think more about joins actually.
I want to be more deliberate about my projections and get back exactly what the view wants, because that's how I'm thinking of it too.
And then as long as I'm wishing for, you know, a pony for my birthday, I want it all to be typed all the way from the front end to the back end.
Yeah.
So.
That's a setup for you.
Where are you?
Right?
So to some extent, it will be typed.
pushcx https://github.com/joeldrapper/…
because, and this is partly why we want to have this SQLite parser rather than like Rails, Rails has like very primitive SQL parsing that it does with regex.
Yes.
And it can basically only pass some of the like create table schemas that it generates.
Yeah.
And we're trying to just basically pass every piece of valid SQLite perfectly so that we can fully understand it.
And that is partly... And why do you want your software to be correct, as opposed to just slapping strings together?
Yeah.
So it's partly because we want to be able to read the schema dumps so that we can understand the schema of your database properly.
And also, no matter how you set that database up, if you have written your SQL queries by hand,
And there's stuff that, you know, isn't standard.
We still want to be able to map all of that.
but it's also partly for dev tools because, we think that basically like a lot of the time SQL is the best tool.
Like it's sometimes it's nice to go through an ORM.
Sometimes it's nice to just go through SQL again, just write your query properly.
that's this comment that I brought up on stream of.
It's sort of a list of every time I have felt like that in our code base where I'm just like, look, I know I can write this in SQL.
Let me write the string that's just the recursive common table expression or the big group by for the stats page you saw or all of these other things where it's just like, sometimes I write them and then I port them to active record.
And sometimes I write them and I'm like, oh, that's never going to port.
And it's just going to be a where string anyways.
Let me call select all.
Yeah.
So basically, there are going to be a few different parts.
The one part is being able to interpret
whatever query language you give us.
You might give us raw SQL.
You might give a SQL that's meant to be a specific fragment.
Like this is specifically the SQL that's meant to be inside a select query.
Right.
Or you might give us some kind of like higher level abstraction, some Ruby DSL, like you get from active record.
Sure.
Yeah, or arrow.
Then we actually query the database, and we're going to get back some results.
And we get them back from SQLite as primitives, like arrays and hashes and things.
At that point, it's optional what objects you map it to.
And essentially, our ORM will be a specialized version of literal data objects.
So essentially, so that you can take one of these queries and say, I want you to treat the result as and like map it to these like literal data objects.
If you look at structured objects on the left.
Thank you.
I hadn't seen that yet because I've only seen a couple of your blue sky posts about literal
And I keep looking at it and going, but I'm used to Sorbet because I worked at Stripe that invented Sorbet.
And so then I kind of tune it out, but yeah, I thought I could maybe cue you up and get you to make the pitch.
So this is basically data.
Okay.
So yeah, if you, that's, that's a struct, structing data in literal, identical apart from data is frozen.
Sure.
Why did you re-implement the data class that just got added in 3.1 or whatever?
Mainly for this prop interface.
You can specify a type and you'll get a runtime error if this type fails when you try to create the object.
You also get better documentation.
If you have a type that is referencing another type or an array of another type,
With Ruby LSP, you'll be able to just command click on your type signature at the top to jump to that class.
Joel, I really learned types in Haskell, and I'm suspicious of any type signature that has a new line character in it.
Where are you seeing that?
This new line here?
Between the two?
Yeah.
Aren't you supposed to slam...
I know, but...
Right.
But the user has the type name age, right?
Yes, it does.
Or are you also doing nominal typing?
So if I made a class, I don't know, oauth user that had the same props, would they be considered the same type?
It's totally structural.
No, they wouldn't be considered the same type.
It is nominal.
Yeah, there's a bunch of other things that you can do with the prop here.
So for example, you could pass a block to coerce the value that you give the name into a string.
And that block would be executed on the value before it gets checked if it's a string.
So you could imagine maybe passing the block ampersand 2s.
You can also say, like, reader, public, protected, or private.
Writer, public, protected, private.
Predicate, public, protected, private.
And I'm trying to think what other options.
You can specify default value.
So if you make the type nilable.
Off the prop, did these classes have, can you define arbitrary functions on them?
I assume so, because they're just Ruby classes.
Yeah, they're just Ruby classes.
Essentially, if you go to properties on the left, so probably going to give you a few more examples of this.
Structured objects are just a more stricter instance of literal properties.
essentially assuming that if you have a structured object, the data
that you put in that object defines that object.
So structured objects like Data Instruct will have equality.
By default, the equality methods are generated.
Hash equality is generated.
You can serialize them.
You can marshal dump them.
You can turn them into a hash.
And that's all generated from the properties that you specify.
Would it be fair to think of this as like an adder accessor that comes with a type signature?
Yes.
Okay.
Yeah, exactly.
So it will generate your initializer, your reader, your writer, and your predicate method for you.
And it will type check on write.
Hmm.
I can see how that gets you a little more flexibility than just specifying a type signature on initialize to name off the properties.
Yes.
Yeah.
Cause you can also then enumerate the properties, like the two H method that gets defined for you on, on a structured object is based on enumerating that list of properties.
01:51:15Do you have much experience in functional programming?
arh68 I wonder what `prop :name, Person` would do hm
Because this strikes me as where you get, if you take an OO background and you want to add types, rather than if you are trying to come from a functional background and add types to OO, if that makes sense.
Yeah, so I don't have a functional background, but I'm a fan of functional programming.
arh68 like using user types
I've learned quite a bit about it.
Particularly, my introduction was in Kotlin, actually, which is a very interesting language because it combines functional and object-oriented in a type-safe way.
But yeah, I've kind of explored both.
ARH is asking, and I believe this is a feature of Littoral that... Yeah.
Are you able to see the Twitch chat as well?
Yeah, I can see it.
So the question is, what would prop name person do?
And I'm guessing that's over here on the types, because I've gotten the impression this is all extensible, right?
Yeah, exactly.
So it would define the initializer to take the keyword argument name.
And when you initialize this object with a name, it would send triple equals to person with the value to check that it is truthy.
01:52:48Did you cut a gem for this?
Yeah, literal.
arh68 cool! thx joel
I wasn't sure if it was released yet.
It's a shame I can't... Take over the stream.
It would be nice if you could.
There's some kind of deep magic you can do with OBS to do so, but I don't know what it is, and that's a little bit more complicated than I want to try and set up on screen.
You should try... Have you tried the Tuple Linux version?
I don't know what that is now.
It's a.
Source Ruby gems.
Oh yeah, I can't remember how this was.
OK, I needed a URL.
01:53:50Look at that and use the right.org. OK, so now we can play. And then do we want to add a class person? Yeah, sure. So both of these need to either extend literal properties or inherit from something like literal data. So what I want here, I want literal object. Yeah, you could do that. So let's make this name a string, and we'll give them an age, which would be an integer. And then this can be a user, and then who, and then prop. I don't know. What else do we care about for users? Username. Username. Let's go on. Yeah. So then if I said, What? erv-r who? Have a user. No, it didn't load. So that's space. Where are we getting cannot load file?
01:55:21What's the? Oh, you have a required literal in here. Yeah, that should do it.
...42User didn't get the literal object on it. It's fine and then let me actually bring this over here so we can see both at once.
...59No code reloading, but so then I could say u equals, let's make a person first, person.new.
Does it do positional or keyword?
Keyword by default.
Okay.
Frici did my bandwidth die a death or is it raining in Chicago?
So we'll say name is Joel.
arh68 F? hmm
It's you, Fritchie.
No, everything looks good here.
Pritchi is referring to the fact that when it rains, my stream goes to hell.
There's just something flaky between the... Yeah, I've lost your stream as well.
Oh, oh no, maybe it's me.
The little stream health graph is fine, but maybe it gave up on... Yeah, I've reloaded.
Now the stream health graph says I'm SOL.
01:56:56arh68 that's how you know it's live, when it crashes SeriousSloth
ForstPenguin yup, F
zero kilobytes oh yeah even obs says i'm screwed so you could just give it a minute and see if it recovers let me actually respond because i they probably can't hear us
01:57:28pushcx Argh, sorry, some kind of upload speed issue? Not raining...
And then I can...
If this isn't going to start... Let me stop and start the stream.
Okay.
You'll stay connected.
Yeah.
I keep my laptop very streamable all the time because I probably spend...
pushcx OK, how about now?
maybe three or four hours a day pairing on tuple wow maybe more than that actually yeah like steven and i pair on on open source stuff for many hours a day all right it sounds like we're back i stopped and started the stream so there will be two vods for this one but
My janky blog archive is actually not so janky.
Yeah.
Yeah.
I can see it.
Great.
chamlis_ back for me
Frici seems to be better
arh68 HeyGuys cool beans ? workin i guess
pushcx fingers crossed
So we got a person and we made Joel and I don't know your age, so I just made up a number and then I made a C user dot new, and we will say the person is P and the username will be Joel river.
So ARH, does this answer your question of what this interface ends up looking like?
And then we have like a, we can ask the user their age.
Oh, it's not a public property by default?
No.
Frici I am building a buffer though so unsure where this will go CaitThinking
So it is on literal data, but on literal object, you would have to add reader public to your property definition, and then it will generate your reader for you.
01:59:21So is that... So just put comma reader as a keyword argument, and then the symbol public.
...34And then I'm going to have to bounce this, right?
Because reload is a clever Rails thing.
Yeah.
It has to be the symbol public.
Oh, symbol public.
arh68 heck ya man that's great
Yeah, that would just otherwise be a... A method call.
Yeah.
Yeah, so then it will let you read those.
So readers are public by default on literal data and literal struts, but not on
all objects the mixing is just doesn't really try to assume anything did i do something wrong here you haven't made age have a reader only person and username has a reader oh i'm calling the wrong object that's a yeah age is the prop on person all right so what i mean to say is user.username
Okay.
Probably the easiest thing is to just replace literal object with literal struct or something.
Because then you get writers as well.
You'll get everything by default.
Wow, I was going to say data, because I lean towards, unless there's a reason to make it beautiful.
Yeah, sure.
Yeah, same.
And that's exactly why data exists.
Now you don't need to specify read a public.
You can, but you don't need to.
02:00:57arh68 u dot person dot age
What did I blow up?
Oh, I ran the code in the wrong order.
Now we know what a type error looks like.
Yeah, I did that just so that you could demo.
Yeah.
So the type errors, we worked really hard on these.
They show you the method that you called, user initialize, and the keyword that was wrong, person, what was expected, and where you actually got its class and its value.
Not bad.
So what if I say something more complicated, like I say user, and then I do an inline person.
What kind of error am I going to get if I do say name Joel, age, or no.
02:01:56Undefined method. Oh, I called person instead of person.new.
02:02:05This should give you a pretty good error. Yeah. I mean, it's not quite the Elm or Rust level of underlining my thing, but that's really quite useful. So it's clear it's person initialized. And for age, it expected an integer and got a string named foo. Yeah. So if you jump back to the code, I'll try to explain how some of this works. So what it's doing is it's sending triple equals to the second argument, which is the type. And so for these simple cases, we're just using what's built into Ruby, which is triple equals on a class, checks that the object given is a member of that class, is an instance of that class. Right. Well, isn't it more than that? So if I said a module, then anything that includes that module would be acceptable, right? Yes, exactly. But you can also have objects that respond to triple equals. So for example, the string hello responds to triple equals. And it will only be true if you actually pass it in the exact string hello. Can I write arbitrary predicates? This prop probably won't work because you need to extend literal properties on that. That module doesn't have the method prop. Right. I think if you extend the dual properties, it will work. Let's get these things together because I'm going to be switching again. But inheriting here, now it can't be a module. Line 5 is wrong. It needs to extend instead of inherit from the dual properties. Thank you. I am struggling with my bin keystrokes to get the terminal back. What did I end up doing?
02:04:27arh68 oh wow so you can `prop :age, ..range..` ? that'd be cool
So I can push these things around.
All right.
So this one is extend.
I never remember the difference with modules.
You think that'll work?
Yeah, that'll probably work.
All right.
Let's try it.
I hope so.
One small.
What do we got?
Did I type something wrong?
Syntax error unexpected.
Oh, inherit.
No, sorry.
Line 5 is still wrong.
So you need to, line 10 needs to be include ageable, and line five needs to be, you need to drop that down to a new line, because you can't inherit from a class.
And you want to include or extend?
Extend.
02:05:19Yeah, so ARH is basically asking if he can have, actually I don't remember ARH's gender anymore, if they can have,
Basically... Like generics?
Well, no, not generics.
I'm not looking at the chat.
The values...
Yes, yes, you can use ranges.
What is it?
Refinement types.
That's what I'm trying to ask.
I don't know what that is.
Refinement types allow you to type based on the values, basically.
And I am... That's not the CS person definition.
Yeah.
I'm going to send you some code.
Can you copy-paste through Discord?
Yes.
arh68 === is quite the equals
I just have to open Discord again.
And I'm going to mute it.
As Pritchi pointed out, nobody needs to hear it go deedly-dee for me.
It might be easier if I can send you stuff in...
discord yeah so let's just i don't know if you want to just undo this back to where it was working i think well this is actually an interesting failure that i can't have a module if superclass is a yeah that that is i think or is superclass your variable yeah that's a variable not a yeah that's yeah this is an internal error so
The issue here is that we're trying to define a property on the module ageable, but modules can't have properties because they can't be instantiated.
So if you wanted to make this module work, you would have to use the included hook to call prop on the person itself, if that makes sense.
Yeah.
But easier than that, you could just
move prop back down into person could but i'm really curious about code sharing yeah if you do underscore dot prop now it will work underscore dot prop okay well then let's because you need to call it on the on the class yeah sure
Hey, so now can I say that?
That should fix it.
I think that would fix it.
So let's see the error.
Missing keyword name, right?
So name, colon, it's this one, Peter.
I was able to instantiate without an age.
Maybe the hook hasn't worked.
The included hook hasn't worked.
Unknown key age.
Yeah, it hasn't.
It hasn't defined it.
Is there...
It's self.included, isn't it?
It's something like that.
I was just putting the mouse on it because... That'll be it.
So let's go here.
Need to make this reloadable or get this in some kind of reloadable environment.
I wouldn't mind.
Yeah, this should do it.
There you go.
Yeah, so I get the error I expected.
Okay, so we can code reuse those.
Nice.
And so can I say integer...
So to ARH's specific question, could I say that the valid number integers for age are 18 and up?
Yeah, so to do that, you need an object that responds to triple equals that will be false if it's not an integer between over 18 and true otherwise.
And so you can get that just by doing underscore integer to call it as a method and then pass in this.
So yeah, if you do underscore integer and then pass it 18 dot dot.
That doesn't have to be a proc or something?
Because the range 18 dot dot responds to triple
equals.
We're going to check that it is an integer, and then we're going to check that its value is 18 dot dot, just by calling 18 dot dot with our value.
02:10:15What did I do?
So I commented out the ageable bit.
Unknown keyword, age.
Oh, it's pop, not prop.
How did I not get it?
Oh, because pop is... That's weird that that's in scope.
Is it?
Is it that low?
I'm guessing it is.
Person, ageable.
Let's change that back to person.
And then... Oh-ho-ho.
Take a look, ARH.
Yeah.
So this...
The integer is just a fancy way of doing a constraint.
And so we'll add another constraint.
arh68 VoHiYo coooool beans
In underscore integer, in that method call after 18 dot dot, if you put comma and then say like even question mark as a keyword,
and then set it to like true.
You'd have to put the colon on the other side and then do true.
You can actually just put the colon on the other side and it's fine.
You can remove the rocket.
Sorry, so I'm going even question mark colon true?
Yes.
Oh, I can't type.
Yeah.
So now you've got three nested types.
The first type is what underscore integer returns, which is a constraint object.
That will check that the object that you give it is an integer.
And then it will check that it is 18 dot dot by calling triple equals on 18 dot dot.
And then it will call the method even on it and check that the return value from that matches the type true.
And if you call triple equals on true, it's only true if you give it true things.
So this is now constrained to only if you have an even age above 18 by using three nested types.
arh68 types on types on types HahaThink
And if you assigned the result of this underscore integer to a constant, like age constraint equals underscore integer, you could use it in a case where or a case in as well.
Oh, that's interesting.
In fact, with case when, you don't even need to put it on a constant, but case in you do because...
underscore integer isn't here.
So if you do extend literal types, cause you're just calling this on kernel now.
Yeah.
literal, literal types.
Yeah, there we go.
Not so bad.
Yeah.
So now, so now age constraint actually is this object constraint, and
it has triple equals on it.
So you can try calling triple equals with various values.
But also, if you just have a case.
Can you say more about this underscore convention?
Is it intended to say it's a constraint that
is already based on this primitive type, and we expect those to be so common that we've included these as a convenience?
Exactly, yeah.
It's basically the way of...
So I wish that I could do this without the underscore, and I could, except that...
Ruby already defines array.
If you call the capital A array method, that's already a thing to coerce an array.
So I didn't want to override that and break it.
Yeah, so if you just put parentheses after array, it will call the array method.
And that's the one I didn't want to override.
Sure.
so if you put like array one it will wrap one in an array but if you put if you pass an array of one to it it will not wrap it so this is like it's been a hundred since i've used that function yeah yeah so basically so what's actually happening here is sort of string is the special case where if you're passing a class to a prop it says all right well we're going to
Oh, no, it's not.
It's not a special case at all.
It doesn't even special case it.
Because your API is triple equals.
Oh, that's clever.
Yeah.
Yeah.
Ah.
So here's your new PDFL.
If you have...
This is very nice.
Actually, can you bring up the documentation again?
Yeah.
I just want to walk you through a few other types.
Let's...
Swap it over and make it bigger.
How's the font size for you?
Yeah, great.
If you go to built-in types.
02:16:10You know, Eric, what seems really nice is leaning into the Ruby triple equals like that is kind of taking the best of Ruby as opposed to
arh68 seems pretty ... seamless Kappa lol I am a Liker
sorbet is very clearly laid on top and executing on a different layer than right your code literal is interesting because we just very casually in a very ruby way move between i hesitate to say compile time but you know usually types are checked at compile time and we just kind of wandered between compile time and run time in the exact same way that like monkey patching does
Yeah, I mean, it's pretty much all at runtime.
But there are no allocations at runtime from any of these.
When you call triple equals, none of these will allocate.
What?
I saw you making posts about doing weird things to avoid allocations, but you're saying none of these constraints?
So when you actually call, they allocate a boot, but once they've allocated an object, when you call them, they don't allocate.
They're all zero allocations.
We can look at the test that verifies that.
I would love to see the test that verifies that, actually.
That's very clever.
Yeah, we can dig into the code if you want to bring it up.
Yeah, that's the one.
So the test, it's the allocations test.
Oh, clever.
AllocationsTest.rb.
So this one isn't a QuickDraw test.
This is just a file that I run separately because QuickDraw runs in parallel and so counting allocations wouldn't work.
Makes sense.
But I've just got this assert allocations method that will raise if these don't come back with zero.
02:18:18How are you... So I understand how, like, this... I was going to say, I can understand how this, like, truthy and falsy would avoid allocations, but you create this tuple string and integer, and you don't allocate? Yeah, so, because you only allocate to create it, but then calling triple equals on it doesn't allocate anything. Because you're just checking over... Ah, ah, I see what you're saying. Okay, so it's more limited than I was just assuming, but still, that's quite nice. Yeah, and actually, if you look at the tuple thing, there's a new optimization coming to tuple. So tuple... In fact, maybe we should just open the... Actually, let's just look at this one for a second. So this one is basically saying either or, right? String or integer. Sorry, there must be a minute of... can you reload your stream? Cause you're clearly like 12 seconds behind and that'll catch you back up. If we had any kind of instability, you end up behind and there's no way to catch up on Twitch. Okay. I think I might be caught up now. All right. So I'm going to click truthy now. Yeah. You're about five seconds. All right. So that's, that's not so bad. Okay. All right. So you were looking at this, assertion about tuple. Yeah, so a tuple is a generic that takes n subtypes, right, in this case, string and integer. Sure. And then it's basically saying this or this. But if you have a tuple and you insert into it like a thousand different strings, let's say you have a tuple of, I don't know, cities around the world, whatever, something like that. You're saying, if this string is exactly one of these city names, then this type is valid, otherwise it's false. So you create this tuple Traditionally, to maintain the triple equals interface, we would have to, at runtime, when you try to check, you give me a city name and I want to check, is it in the list? Because of the contract with tuple, it's going to check every single item in the tuple, one by one, calling triple equals on it until it finds one that returns true. But obviously, if you have thousands of them, that doesn't make any sense. So there's an optimization. The built-in primitives in Ruby, array, string, symbol, etc., they have this quality that is triple equals equality is the same as hash equality. So if I am comparing two strings and I say string A triple equals string B, that will always be the same as if I do the hash equality, which is dot EQL question mark, or essentially dot hash equals equals other dot hash. Which means... I don't think I knew that's how that was implemented, that it was... I don't know if it's how it's implemented, but it is true that they will always be true, like they... the results are exactly the same. And that means that we can make the assumption that if you've created a tuple with a literal, like a string or a symbol or a number, we don't need to follow the interface that we promised to everything else that you put in a tuple, which is that we're gonna call triple equals on you. Instead, we can put it in a set And we can very quickly check whether it's a member of that set. So if you look at what, if you open the file tuple underscore type. Yeah, I meant to navigate by the right side. I just tuple in. Yeah, so that one will take you here and that just returns a tuple type dot new. So you can see in triple equals. A tuple must be an array, so that's our first check. We're going to return false unless it's an array. And we maintain the triple equals thing here. We're calling array triple equals value. Wait, this doesn't have my optimization. I think it might be a PR. I think I haven't merged it yet. Do you want to just see if there will be an open PR? Perfect testing. That's not you. That is not me. Do you have a branch? Wait, maybe it does have my... Can you click on Closed? No, I definitely did this. I was going to say, did you close it? I know I did this. Let's sort by most recently updated. Yeah, optimize primitive unions. I merged it. That's the one. Did you merge at the command line? I thought I did. Well, it says that you merged. Well, let's just look at the diff here, because this definitely has the optimization.
02:23:53chamlis_ just came in, wouldn't that optimisation be for union?
OK, this is better.
This is union, not tuple.
That's the difference.
Oh my gosh, I was looking at the wrong type.
Yes, I meant union.
Do you want to just jump to union type.rb?
Because I messed that up.
No, no, that's fine.
Then we don't have to see it in a tiny half window.
So you called it union type.
Yeah.
That's better.
Yeah, that's much better.
OK, so this is an interesting way of splitting this to start with.
And this is for boot performance.
So you create a union with an array of types.
But we just call this a queue.
And then using a while loop, we're going to basically sort everything in that queue into one of two buckets, a types bucket and a primitives bucket.
but in the case that we find another union, so you've like nested one union inside another union, we want to flatten it.
So we just can cap on the queue, the types and the primitives of the other union to allow the queue to like consume everything instead of, so you can't have a nested union.
It's going to, it's going to automatically flatten it.
So the end of this, you have, this types array and this primitives set.
So everything that is an array, a hash, a string, symbol, everything in that case statement goes into the primitive set because we know that we can use hash equality to find it in a one time instead of a one time.
And then if you scroll down to the triple equals method, which is going to be five seconds after I say it.
Yep, exactly.
then you can see that we can early return true if primitives include value.
Aha.
That's a nice little optimization.
Yeah, I tested this with, I think, about 124 items, and it made it 50 times faster.
And even with just two items, it is faster than the alternative where you check each one.
And you can see below line 43 through 47, this is how you iterate over an array without allocating.
How very C-shaped.
So that's then like doing the slower check on types that aren't primitive, so you can't necessarily guarantee that the hash lookup will be equivalent.
02:26:52I know Ruby has a for loop. It allocates? I don't know if the for loop allocates, you might be able to use that. You'd combine 43 and 44, but whatever. Yeah, that's probably true. I don't have much experience using the for loop. I can't think of the last time I saw one in Ruby. It's weird seeing something that's not each. Yeah, .each definitely allocates. Right, because you're passing a block. I would expect that to hit the stack. But .h on a hash doesn't allocate. No? Is this one of those bits where a lot of the built-in types are written in C rather than Ruby? I'm not sure. Okay. Yeah, I'm not sure why. But yeah, this is just an example of how... You saw in the log types.rb, there's a method underscore union that just creates one of these objects. And then this object is set up to receive triple equals. Makes sense.
02:28:08okay so if we want to like pop our discussion stack from literal a bit yeah you were pitching me on sqlite and i mentioned this is our list of this is also our list of like non-portable things we do because it's every place i fell back to raw sql or pass strings in like where clauses, dot or was like what rails six. It was fairly recent. And so we have a bunch of them and then there, I just don't have them loaded in my head. So for me, it's always easier to pass a string than actually compose the queries. Hmm. So then if we moved to what's the name of your new ORM? Plume is the underlying library, and then... Did I lose you, Joel? Maybe he died. He said something about a laptop. Maybe he ran down his battery. Yeah, the voice call apparently dropped. So he's gone. Too bad.
02:29:38oh here we go hello hey there lost you yeah airpods ran out of battery that is the issue that'll get you but i am back on phone so so what i was gonna i was asking plume is your sqlite parser and then what's the name of your orm built on top of it we're hoping for
Frici rip joel
feather, but someone else has the Ruby gem called feather.
So we're trying to negotiate to get feather.
Oh, I'm actually doing...
I actually lost that one.
So the Ruby project I'm working on is called recheck, and I had to be recheck dev, which is like the domain name is recheck.dev, because some random person has the username recheck and very little activity, but
a non-zero amount, and none of it is public, so I have no way to contact them and GitHub politely, and I understand why GitHub doesn't want to be involved in these, was just like, have a nice life.
Namespaces.
Yeah, it's really annoying.
Anyway, so, okay, the hard sell on SQLite.
Well, the hard sell is not so hard if I can have
projections and types with literal that run all the way up through flex that's actually a source of significant pain for me although i guess part of that is just because of one plus ends which precipite mostly handles for me yeah so you can go one of two ways you can either keep trying to
deal with your M plus ones, or you can forget about them because you don't need to worry about M plus ones with SQLite.
If you want to write the code in a way that does M plus one queries, you can just do that.
It's fine.
You can run 10,000 queries in a single request and not worry about it.
It will still be faster than Postgres.
That feels like a sin.
But it's not because actually SQLite is specifically designed for this.
Right, because it's running in process and you just have a file handle.
So you're just doing like fseq.
It's probably running in memory if your database isn't that big.
Like a whole bunch of it will be cached.
your memory and and so it's actually easier to create like these simple indexed queries and just run lots of them because there's no latency between you and your database so that's a very different path towards getting rid of this
wary so yeah i have committed many sins in the name of performance in the lobsters code base because like fully half of our hits are load all of the comments for this one story and display them in a tree and sequel entries don't get along you know they're the montagues and the capulets and recursive ctes make this not so bad so i can say things like
Right, but what's the recursive CTE doing?
It's fetching all the comments as a tree.
And within the database, it's essentially doing n queries.
There's just no latency.
Right, so it's finding all the ones that don't have a parent comment, so they're the top comment of their subthread.
And then the recursive part is, of course, where the parent comment is in the previous set, right?
So that's the heart of the query.
And then the insane part is my performance hack to get the query comments in the order I want.
because comments are sorted and they're not sorted by date.
They are sorted by how many upvotes they have, but it's more complicated.
They are sorted by confidence because if you have a comment that has two upvotes, right?
So the net score is going to be two.
That is probably a better comment
Then a comment that has five upvotes and seven, or I'm sorry, three flags, which would also have a net score of two and confidence order does this math.
right.
And then confidence order path.
is my insane hack to make this work in maria db by recursively building up a string of the confidence order value of each parent comment packed into three bytes because if it gets too long the string causes some kind of performance regression where like once it exceeds
was it like 200 characters or something we had some weird non-linear behavior where it just went straight to hell so that's why i do all this bike packing and strings which you don't normally see because maria db doesn't have an array type that you can compose in a recursive cte postgres does and that contributor rahul is in the middle of porting it but
If I can just fetch each comment one at a time, it doesn't matter.
You should be able to.
I wouldn't even have to have a recursive CTE.
I would just fetch all the ones from the story.
And then I'm building the tree in Ruby, which is part of what I want to avoid.
But maybe it's not so expensive.
Probably not that bad.
How many are we talking?
Ruby's very fast at doing array manipulation and sorting.
But it's not an array.
It's building a nested data structure.
Although I guess we could go back to the Ruby equivalent of this, where we build up an array of the confidence values.
So each comment has an array of the confidence values of all of its parents.
And then you sort by that.
would actually be maybe i didn't have to write this in the first place so the way the code originally worked was it fetched all the comments and then it built a series of nested arrays and every time hacker news linked to a popular thread the site would fall over because it just couldn't do that fast enough yep and
It was so much memory.
So I've actually like, since this is such a hot path, I've gone through over and over to kind of steam things out so that it only has to loop the comments twice on the Rails side, once to load the votes into them and then once to print them in the template.
And I'm aware with like n of 200, flattening loops and all this kind of stuff is a little wild, but it's the one hot path in the code base.
So let me show you the other terrible performance thing I did.
It's just, like, when you realize, like, and honestly, this was the thing that really caught me off guard when I started building out the ORM stuff is, like, my initial approach to this was, like, how can I build, like, it's so easy to run into M plus 1s in Rails.
Yeah.
And they're so natural.
They feel like nice code.
But they're bad.
How can I build a query system that doesn't even allow you to do M plus ones?
But then I realized SQLite thrives on M plus ones.
Because there's no latency.
And it opens so many doors.
Oh, it's an initializer.
That's where I hit it.
So that's...
I think the latency is literally a couple of microseconds.
Right.
That's very tempting because...
The one place that one plus ends are actually kind of painful is moderating is not a separate app.
And so there are always two code paths of what query needs to run for the users versus what query needs to run for the moderators.
arh68 sqlite is kinda anti-sql in some ways lol
And so I have a bunch of scopes called.
And you have to sort of do that right at the top.
right so i built a scope called for presentation that does the joins but then also some of the scopes not comment here but like not on story hidden like they take a user and they apply stuff on it whether or not you're a moderator where did i do that the story base right yeah so the story base
takes whether you're a user, and then it calls this not deleted.
And because moderators can still see deleted stories.
And so this was the way to avoid having to repeat it over and over through the controllers.
But it's because I have to centralize the decision, because there are two code paths, and they both need to avoid one plus n. So
sort of interesting that that idea just goes away it feels it it kind of raises the hair on the back of my neck because it's one of those migrations where it's like oh i never have to worry about that again but then if we ever you can't get back right then it's like oh it was so painful to install i used bullet at first and then i heard about prosopite a couple of years later and it's been a lot of code
Yeah, yeah.
You don't have to do this in SQLite though.
you can always try sqlite and and still do all of this like yeah and still avoid that right sure yeah it just opens that
arh68 ya no Ctrl-Z on that design move PogChamp
option up to you but the the reason i would go for it is like performance and simplicity if you if you have one server and your server is like what four cpus i think you said so you've got yeah eight vcpus total yeah so the the biggest server you can get right now i believe is 384 threads that's probably a little overkill
us so it's nice to have that right that's that's like the theoretical maximum headroom that you could have right well and the other thing is both of these things are curves where lobsters grows
at one rate and Moore's law grows at a higher rate.
And so we're probably fine.
ForstPenguin that's a lot of threats Kappa
So like the headroom is if we start growing faster than Moore's law, okay, well I can multiply that out and be like, all right, then we've probably got six years to figure something better out, which is plenty of time to reinstall prosopite.
Right.
It's also plenty of time for SQLite to get better and for vCPUs to get better.
True.
And for Ruby to get better.
Because given that your vertical limit is, let's just say it's 384 vCPUs, that's a lot.
That's enough to handle, I don't know, with Rails.
Maybe 100,000 requests per second?
I don't know.
Yeah.
It's a lot.
So I was talking about we have one write per second.
We have many, many more reads per second because we are just ridiculously lopsided on that, as you would expect.
So you were talking about performance and the things you get to do.
So let me give you a chance to pitch Flex by showing you the other evil performance thing I've done.
It's just the two evils, okay?
There's that recursive confidence order path.
I will accept that one is a crime that St. Peter will ask me about.
And then so is this, and I named it heinous inline partial.
So we render comments on the page
And because they are nested in the CSS, or I'm sorry, in the HTML structure, it's like OL and then an LI, right, is a comment.
And then it has an OL of all its replies.
And that's how you get the indenting.
But that means we can't just say render collection of all the comments.
Why?
Because it has to be a nested loop so that the OLs and the slash OLs line up.
Oh, I see.
Yeah.
Yeah.
Let me show you the...
This is the third time...
Right, so each of these...
So we have, like, comment, and then every comment has more comments rendered inside it, and it's nested, right?
So I can't just call render with a collection of comments, although I was experimenting with the thing that would allow it.
It's neither here nor there.
Can you just render...
So if I call render...
Render your comments collection, and inside each comment, render the comments collection again.
As long as you don't have infinitely nested comments, it should be fine.
I can't quite picture what you're saying.
Okay, so you have a component called comment.
Yes.
I mean, you say component, but let's switch back to Rails terms.
Let's not jump to Lux just yet, because we're still talking through the nonsense.
Let's imagine you have a partial called comment.
We do, and I'll show you it.
So basically, inside the partial comment, you just iterate over its subcomments and render the partial comment again.
Yes, that is exactly what the codebase did.
before heinous inline partial.
Now, there is an overhead to rendering a partial.
Yeah.
So what if... Do that partial in flex and there's no overhead to rendering.
Let's get there.
So let me show you.
So we have comment.
Which starts out and then it takes all these things.
Cause we have lots of like, this is the God object for the site story comment or the two.
we're kind of Zoroastrian that way.
And so then we have like the comment metadata.
Let's see.
and then in here we have, I wish I could type all of this stuff.
Okay.
So, so that's the individual comment.
Now let's go up and I'll show you the bad thing.
In thread, thread manages, we're printing a whole thread of comments, like a whole story's worth of comments.
Right.
And look at this magic comment called heinous inline partial that names that comment partial.
Because if we inline the comment partial into this, we save on the overhead of hundreds of calls to render partial, which has a meaningful performance difference.
It's like 5% to 10%, which adds up when that's half of the page views on our site.
So then there is this heinous inline partial initializer.
So when you start up, it looks in the views
The abstraction leaks when you edit the comment partial or when you edit thread.html because you're in the middle of it and you don't realize heinous inline partial is happening.
That one is especially bad because then your changes get thrown away.
And so it looks through the template and it finds these fake markup tags and then inserts.
And there's a lot of like, whoa, you didn't do it perfectly.
Let's raise.
arh68 the abstraction leaks :(
And so this is the terrible thing I do for performance on the hot path.
I would like you to tell me how Flex makes all of my crimes go away.
I mean, you just wouldn't have to do any of these hacks with Flex because there's no overhead to rendering another component.
Yeah.
Why is there no overhead?
Because I'm going to have... Because you're just... My thread component.
So let's...
So I've seen Flex and I've played with it.
Let's catch the stream up with a demo that, you know, an example.
In Flex... We're looking at the terrible documentation.
Yes.
I was reading this.
I did go to the beta version.
Yeah.
So everyone, this is release candidate code.
He's wrapped all of HTML up in...
Ruby classes, and so you can specify things like we have nav with items, and an item is another component that is calling other functions that generate in each of these.
So item here isn't even a component.
Item is just a method on the nav component.
Oh, yes, it is.
Yes.
And so in this header component, we render the nav, and it yields itself, and so you can call item on it while you're rendering it.
Nice.
Yeah.
And so if I had a thread component, it could yield itself, and I could call .comment on it.
Or, I mean, I would just have a thread that takes an array of comments or a relation of comments and itself just renders comments, which themselves render a thread.
Hmm.
Okay.
You won't hit as... And then... As long as your stack size is as large as... One full comment thread.
I think our largest thread had like 250, 300 comments.
And that's like the deepest thread?
Oh, the max is now 18, but we had one thread that was like...
Obviously, everything is log normal in social software.
And so there was one thread that had 31 replies deep, I want to say.
that's fine the when you nest in flex it does use the ruby stack because you're just passing a block right so if you if you have like let's say that you had unlimited thread nesting and so you had a thread that was like 50 000 like nested items then you might run into a stack issue
But I'm pretty sure you can just configure Ruby's stack size anyway, and it would probably be fine.
But probably you would want to solve that with a while loop and a queue or something.
And I guess I don't have all the overhead of render because internally the way Flex works is it builds the whole thing.
When you're rendering, it just yields it.
Yeah, it only has the overhead of calling render once to turn all these objects into HTML.
Sort of.
I know there's something about supporting streaming.
Yeah, so if you go back and forth between Flex and ERB, there's a bit of overhead.
If you look at Under the Hood as a page, this is actually the most polished documentation page.
It's like in the middle of the handbook.
I see it.
This is the best I can do to describe how flex actually works.
So this second example is really crucial, the second code example.
So the method div, you can think about it as just pushing a string to an output buffer, which in flex is a string, a mutable string.
But here, we're just using an array.
and then it yields if there's a block given and then it pushes the ending div right so that that's all that it is is that so so oh okay i remember this note about string versus array but it was just about mutability so that you could explain it a little easier
Right.
arh68 good ole StringBuilder SeemsGood
And also so you can look at the buffer and see the history more easily.
But if you scroll down, you can see when we call it, we call ViewTemplate and then we just join the buffer.
Yes.
And this actually goes into how you can implement rendering subcomponents.
So a bit further down, we implement the method render.
Let's just control it.
Should have searched for def render, but it's too late.
There we are.
Nesting components.
Yeah.
So this is how Flex's render method works.
It takes a component and a block, and it just calls the component.
It passes in its own buffer, and it passes the block.
And the example here, def call, you take a buffer and a block, you set the buffer to your instance variable, and then you call the ViewTemplate method.
So I think right at the end, there's an example that's just essentially all of the different parts working together to have nested components rendering.
Like this?
Yes.
Yeah.
So this is basically it.
This is the whole idea.
Flex is much more complicated than this.
But if you had a version of Flex that only supported divs, didn't have HTML safety, didn't have...
This even has attributes, right?
Right.
This is it.
There's... You know, especially with that note at the front about...
Internally, it's a string rather than array.
There's something honest about taking Ruby functions and classes and converting them into a string at just that.
Yes.
That's what we're doing in the,
data-oriented programming style.
Yeah, this doesn't build an AST or anything like that, because that's really slow.
But it does use the Ruby stack and Ruby blocks to make sure that your structure is correct, that everything is HTML-escaped correctly, and that every tag that is opened is also closed correctly.
Yeah, so that's back to heinous inline partial.
The thread code actually...
arh68 i always liked Erlang's deep-lists/io-lists
has to count what changes in comment depth to be able to insert the correct number of close tags, because I'm breaking the abstraction.
And you have to insert whitespace.
Imagine if you didn't want to insert any whitespace in there.
Oh, yeah, that's...
Because sometimes it's important not to insert whitespace, and that can be a real pain in ERB.
Yes.
Yes, it is.
Flex has a tag that you can use when you want whitespace.
And then by default, you have none because, yeah.
And it's literally implemented def whitespace, buffer, shovel, a space character, end.
That's it.
God, it takes me back to the...
very 90s html i used to write where everything was just absolutely shotgunned with ampersand nbsp to space things out yeah yeah the it was like it was that and br tags right and one by one spacer gifs that had heightened widths on them and between the three of them you could build anything yeah it's kind of funny to go the opposite direction
I was tempted to put an NBSP method in Flex.
That would be a very cute name.
The youths would not understand it.
But yeah, no, someone came by one of my early streams and they were like, what is this editor?
And I was like, well, it's Vim and I've been using it for a while.
And he was like, why?
And I was like, come on, I started my VimRC in like 96.
And he was like, your editor config is older than me.
That was a weird moment.
You'd have to explain NBSP so many times, but not to me.
Yeah.
I was there.
Yeah.
yeah this is this is basically it and flex does a lot of performance stuff so it's way more complicated in this for example it will check if you have no content and no attributes it can do a single string push with the open and closing tag for like it could have oh clever
And so it uses conditionals to have these very optimized code paths where it doesn't necessarily need to do so many concatenations.
But it also, because it's structural...
It's funny how Ruby is so high level that...
You want to have these branching code paths.
And when I write assembly, it's all about get rid of the branches.
It's better to do more work and not branch because you don't want to blow out your...
I'm blanking on the term.
The lookahead cache.
No, the lookahead cache that the processor is doing.
The prospective execution.
Okay, yeah.
That's interesting.
Yeah.
Yeah, branching makes a measurable performance difference.
When you have empty tags, you can just push them in one go.
02:57:48It has the advantage of a structural templating system without the performance disadvantage.
It never needs to make an AST.
The AST is just the way that you wrote your Ruby blocks.
arh68 speculative execution: the final boss of abstraction leaks LUL
But it can still do structural things like, for example, it knows if you are applying an href attribute to the a tag.
It knows that you're doing that.
There's no other way to do it except by going through the A method.
And so it has a code path to make sure that that href can't start with JavaScript colon.
Yeah.
That was one of the things I really liked.
Once I got what Flex was doing... Yeah.
pushcx absolutely
It was like, oh, you know, exactly what level of abstraction you're operating at so that you can apply the correct escaping for.
Cause HTML is this, not only is it already a markup language, it's this nested markup language where you can also have CSS in it and also have JS in it and also have, and so.
a templating system that always knows what level of abstraction it's at instead of jumping in and out of structure and in between it's just strings and strings and strings it's very attractive because we haven't been bitten by a cross-site scripting or an injection attack but we have a couple of things like this raw that
So I have break man installed and anytime anyone gets anywhere near these or the other sequel things, it starts screaming, which is great.
That's what it should do.
But it scares all the newbies because they don't know that all they did was a harmless change to a signature.
Yep.
We recently changed the way that raw output works in Flex, which I think is much better.
So it used to have this method called unsafe raw.
It was just named to warn you as you were using it, which
didn't really work very well yeah no it's we we just saw that with rahul who was new to ruby where he was like oh brakeman is complaining so i will call safe on the string to mark it as safe and i think that might even have been a hint from brakeman somewhere along the way but he had the exact opposite idea it was doing and so he was introducing
an injection attack by calling a function called safe because safe is marking it as safe not make it safe yeah it's that like adjective verse verb which is especially tricky for folks who are writing english as a second language we use safe to mark things as safe ah see now you're gonna have that bug especially with your esl coders but yeah well
Yeah.
I couldn't think of an alternative.
It feels really good, but if you call it like mark safe or safe.
Mark safe might be good.
Yeah.
Yeah.
Yeah.
I mean, I'm, I'm down.
Or if you make it an attribute, like I, you know, like if it was string dot I under, I escaped this equals true, that would be, then the code would reflect what the programmer is expressing.
or what the programmer should be thinking they're expressing right but safe gets straight into that like are you saying it's safe or that it needs to be made safe it's that that is verse should in imperative programming languages yeah yeah i think
Grammatically speaking, safe is declaring something is safe.
It is not saying make safe because... Well, but it's...
Safe isn't a verb, but I understand like it's... English is my first language.
Like make safe is valid.
So am I...
arh68 seems like flammable / inflammable
making it down case or am i saying it is down case because this is the exact opposite of your safe yeah we it's you wrap it so you would say raw space safe space foo like this yes
arh68 i mean if u gotta say the word ... hmm
And so if you just pass foo to raw, it will complain at you and say raw only supports a safe object or in Rails, an active support safe buffer, I think they call it, which is if you call .html safe on it.
So this one then, well, I hope you, can we see that error message?
Let's go find that error message and judge it.
It's in the flex repo.
I'm trying to think which...
It will be in stml.rb, I expect.
There it is.
You passed an unsafe object to raw.
Yeah.
I'm sure it could be better.
Yeah, I'm advocating for making it a little wordier, or...
The other power move with error messages nowadays is including a URL, because then you can have 300 words to say.
Yeah, like loads of documentation on it.
Yeah.
Hey, this is what's going on.
This is the context.
This is why it thinks that.
Yeah, to be honest, we should probably have a whole page that is just on how to safely do raw output that explains how to escape things.
Yeah.
But unless you call the method raw with something that is already safe, which means you've called safe or you've marked with .html safe in Rails, in theory, and there might be some bugs, but I hope there aren't, in theory, it's not possible to introduce
any HTML safety issues in flags.
arh68 a la dangerouslySetInnerHTML hehe
No matter where you put stuff, in attributes,
It just shouldn't be possible because everything is HTML escaped from its context.
And that varies as well.
pushcx `:i_was_there_3000_years_ago:`
So we HTML escape content with ERB, which has an HTML escape function, but with attributes, we already know that we put you in double quotes.
So the only character we need to escape within the attribute is the double quote character.
So there's a G sub to escape that.
So you can actually put HTML inside your attributes.
Sure.
And it's fine.
It won't complain because unless you hit that double quote, it's not going to escape your HTML attribute value.
So let me ask a really practical question.
How does...
Let's wave a magic wand and all of Lobster's HTML is rewritten into flex.
There's no ERB anymore.
Okay.
Rails 8 has leaned hard into, I mean, actually it happened more like 6 or 7, but 6.
Instead of doing full page caching, let's do Russian doll caching on fragments of a page.
I haven't seen anything about caching in flex.
And I want to do more of it because let me show you why, because you'll, you'll chuckle at this.
So we have engine X in front of Puma and action pack and the full page caching gem was extracted from rails.
Ah, from four, not six.
And the last commit was a couple of years ago.
and actually really the last commit was like four years ago okay so because it's just there was like a small i don't even know what it was like they tweaked some tiny thing and then like the last actual code change is more like four years ago and so it's like i i saw that and i was like oh the clock is ticking on how long this works but a huge amount of our traffic is
served by Nginx without touching Rails.
And so even if SQLite is incredibly more performant using Feather or TBD, to talk to SQLite, it would be nice to have caching, say on comment text, which is rendered markdown.
Because we currently cache it at the wrong level of abstraction.
You, you want to cache it in the views rather than, yeah, we cache it at the model layer.
So we render the mark down and then we have a, you know, the comment, you store the rendered.
Yep.
And yep.
it's fine, it works, but every table is much bigger than it needs to be.
And especially for the comment text, if we had Russian doll caching set up, I would just render them and put a one-year expiry on them and come back later.
right yeah and and you can use sqlite for that and then that's really cheap as well because it's on disk but it's still incredibly fast well as long as it's the web app talking to it what would be even nicer is if i could get but i would have to write an nginx module for this is if i could write full pages to the nginx module right to us like a sqlite database because so one of the problems that happens with us is and it's getting worse because the site is getting wider
there are something like 250 000 unique urls on the site without getting into there's an infinite number in the query string so it's there's a hundred thousand stories there's a hundred thousand users there's pagination of various things so call it like low hundreds of thousands when a really aggressive
bot with multiple ips to bypass our rate limiting spiders the site widely we fill that cache and so that's just a set of nested directories on disk that nginx can read out if that's how the page caching works but then the cron job that expires them that's just calling find any file modified more than a couple minutes ago and delete it
can run into an issue where the cron job doesn't finish in two minutes.
And then there are two of them running, and the spider is probably still going.
And this starts getting ugly.
And it's getting uglier the more URLs we have and the bigger bots we get attention from and the less well-behaved the AI scrapers are.
And so then I really want full page cache
to SQLite for NGINX to read out of, but as far as I can tell, there is no NGINX module to read pages out of a cache like that.
Right.
I don't know how to solve that one.
To go back a bit, NGINX doesn't have caching yet.
It's pretty fast, but it doesn't have caching yet.
I mean, you've seen the kind of evil code I'm allowed to write, and then you showed me this, and I can put a cache in this.
Right?
The challenge is, how do you invalidate it?
Because you need to find all of the dependent files
Ideally, you need to know everything.
You want your key to be all of the mutable variables used by any nested layer.
Realistically, this is a series of timestamps.
So that's a challenge.
I think that
it would still probably be worth doing some caching with... Well, I mean, your other really good answer could be, is Flex just like 40 times faster than ERB?
Because at that point, I stopped caring about caching for a couple of years again.
To some extent, it is.
So on my Mac, on a single core, Flex benchmarks 1.4 gigabits per second.
So that is enough in theory to saturate your bandwidth of your server.
That's gigabytes of rendered HTML?
Gigabits.
Gigabits.
Gigabits.
Okay.
So that's 200 megabytes-ish?
No, more than that.
Isn't it?
1.4.
I'm failing at math in my head here.
Let's bring in the calculator.
No, you're right.
It's about 200 megabytes.
Yeah.
03:12:06However, right, what if you have some... Was that, like, flat HTML? Was it deeply nested? Because you're using the stack. Yeah. And especially if you're, like, going up and down the stack. Yeah, I'm curious. It's in bench.rb and then the associated fixtures. It's not necessarily, like, it is a micro benchmark. Sure. But it does some attributes. It's bench.rb, yeah. And then it loads in fixture slash page and fixture slash layout. So those are the two files. Can I, how do I get up to it? Oh. Okay, sure. So these are quite small.
03:13:04But it's getting into, like, it's not as deeply nested as I'd like to see, but it's a reasonable amount of elements. Yep. So we hit the layout, we hit the 100 tables with TRs and TDs and attributes inside them. So this comes out on my laptop, 1.4 gigabits a second. Which is, I think, fast enough. But the thing that you mentioned, which I think is really relevant here, is you're not necessarily even using Flex to render some of this stuff, right? You might be going out to some Markdown renderer. Yeah. Or something custom. Yeah. So... The challenge is how do you bust the cache when it should be reset? Like what if you change one of these files and now the cache should be reset? I think the simplest answer to that is part of the cache key is just a constant that is set when you boot the application. that's fair yeah like that so it saves you from thinking about so much of it where you don't have to think for each code change am i actually touching the key yeah right and that's fine i see it i think that would get you a lot of bang for your buck if that makes sense without having to try to figure out and like build up a render dependency tree But yeah, then you could pass in your own cache keys for the bits that you care about, like this database object or whatever, this date. Right. The last edited time on the comment or the last comment posted time on the story. Yeah. Right. Yep. And then it would essentially be like you can have a method called cache that basically checks if it has something in the cache and just does raw safe. if it finds something in the cache, or alternatively does capture and puts it in the cache. So yeah, you could implement it pretty quickly. That's not so bad. Yeah. Probably in maybe 10 lines of Ruby, you could implement basic caching. So maybe we should just add it. Yeah, maybe you should. And just have like... Document it? Well, because it's going to be a common question because... rails yeah the way it kind of shotguns it throughout your views you think about it a lot yeah the the reason that i haven't done it is i have you've seen what i'm willing to do with ten lines of ruby so this run into a situation where i've needed it and i think a lot of people probably reach for for russian doll caching when they don't actually need it in the views and actually the complexity that introduces is it worth the squeeze it like it is for us because the markdown rendering is yeah even just for a typical short comment is right at least tens of milliseconds and realistically it's more like a hundred and so we cannot do that in any at view time that's so interesting that it takes so long to render markdown there's so aside from it's doing lots of safety we have Patches on top of it to do things like you can mention someone's username. And so if you say like cat push the X and then we link it. Yep. And so then we have to, it's, it's a little ugly right now. but we start the render. We, when the full AST is loaded, we walk it to look for things like usernames and then insert links and then. resume the rendering process and right actually the common mark gem is the only gem besides rails where we have pinned a version in the gem file because they made a breaking change to the api and now we're like a year behind and there have been zero security issues with the old version but the day there is i'm having a very bad day because then i have to like put the site into read-only mode while i update to the new api the issue is hanging out It's not the end of the world. And I think the new API is even a little nicer for what we want to do. But when you reach that deep into how a dependency works, your dependency on it gets a little brittle. That makes sense. Yeah, for those reasons especially, I think we should add caching to Flex. But for the most part, where you would normally think, oh, I should cache this because it's going to be slow, you probably don't need to. If you're caching to cover n plus 1s, which unfortunately a lot of Rails developers do, Either just get rid of your M plus ones or switch to SQLite and then don't worry about it. If you're caching to cover partial rendering being slow, well, in Flex, there isn't any partial rendering, so it's not a problem. Yeah. No, we're doing heinous things because partial rendering is slow. Yeah. Yeah, so... You would probably not need to introduce caching aside from for the markdown thing. And it would still be faster than what you have right now, I expect. Yeah. And you could do that with just... you're just having a component for your comment and a component for your thread. And what I like to do is in the base component or the application component, whatever you call it, the one that you inherit from everywhere, extend literal properties. And then you can say at the top of your component, what you want your component to have so instead of passing in locals and i can't remember how you even do it now with like strict locals and stuff with these magic comments but yeah it's exactly what it is it's a magic comment i can bring it back up it was a views but you can you can actually do type checking at every component so you can type check you can say attractive because i have this property comments and it expects a active record relation of comment and you can actually use a literal has a generic for active record relation that you can pass a comment and it gives you back a type for an active record relation and it will actually check that it is an active record relation and that the it's a relation of comments That's nice. Yeah, because this is... I had my own version of this, actually, already. If we went back in the... Where's the... So if I go to blame, I can jump back and find it fast. Oh, no, didn't I have it here? Maybe I had it on thread. I had my own version of this that was basically arrays if the needed variables weren't present. One of the things... Right. One of the things that's really attractive, so moving off of performance, because as fun as numbers are, Lobster's is small enough that we don't have giant performance issues. We just have some performance issues, partially because we are low budget. But I really like that because there's no longer the... strange implicit coupling between controllers and views based on instance variables they can be tested in isolation because we functionally can't do that now in rails it's just not something you can do very cleanly yes Yeah, it's so easy to do this. And actually, there's a test helper that I need to add that makes it even easier that I've been using at work. The tricky thing from when you're testing Flex is you get used to, when I render a Flex component, if I pass a block to it, I can use HTML inside that block. But Flex is just yielding. So if you're not already in a context that has those methods, like those HTML methods, then it's not going to work, if that makes sense. The block that you pass when you render a component is an instance exec on the component. It's just yielded. So if you're in a test, you can't pass a block to a component and then put a heading inside it or something, if that makes sense. Because your test helper doesn't include H1. Right. So the thing that I... Yeah, that's a little awkward. I can see it. Yeah, it's really awkward. So the helper that I've made is basically you can say like flex do and then inside that you can then render your component or whatever and your instance execing at that point. That's nice because it's not... Because that limitation was sort of forcing you to test components completely in isolation and there's lots of them. It doesn't make a sense for me to render a thread without any comments in it. But that helper, yeah, please add that to the talks. Yeah, I will. I'm just sending you some code right now that is basically like the skeleton of what one of these would look like if you were using literal. Sure, let me bring him back up here. Hold on, I need to inherit from... This is what Joel has just sent me. I forgot to inherit from component space, but... so it would be like yeah something like that all right so oh that's actually attractive it is so easy to lose track of these especially around our god object because they have like so many little booleans and tweaks and things right exactly so you can see immediately just by looking at the top of this file what all of its properties are and what they are like i and and if you're using ruby lsp you can command click or like what go to definition on comment in those parentheses after active record relation to jump straight to the comment model That's, that's quite nice. I don't cause I'm old and I'm using vim, not even Neo vim, but, you know, as long as I have you, I have a one-on-one flex question. Okay. where's. By the way, if you want to experiment with this, we could do another one and actually bring Flex in and see if we can get it rendering comments. Let me think about that a second. I don't know how often you do these streams, but I could. I do them Mondays and Thursdays. And the Monday one is 2 p.m. Chicago time, and Thursday is 9 a.m. Chicago time. And I believe... I have no idea where you are, actually. I'm in the UK. Okay. It was my guess, but then I met you for Madison Ruby, so. Yeah.
03:25:50Let's see. So off stream, I'm peeking at my, I mentioned I played with a toy thing. why are layouts a different object than components or is it just a convention because i don't like them being a different object to components have you seen the new documentation on layouts it it's changed fairly recently if you look at layouts under handbook how recently like last couple of days couple weeks Last few weeks, I think, or maybe even last few months. It may have changed just after I looked, because the last time I was playing with this was 12 days ago. So my perspective is that layouts should just be... components that they shouldn't there shouldn't be anything special about them right and so there's a few ways you can do it this first one is layout through composition which is if you look at the code example i think there's probably a bit of a lag but this just shows the controller the view in the layout so you render your index view your index renders the layout component and inside it puts some content Right. This distinction between layout and component, and are they inheriting from views base versus components base? So the distinction, I don't want to, I don't like to draw a distinction between layouts and components. A layout just is a component. I do like to draw a distinction between views and components because views are rendered from controllers. They are the entry point to your rendering process. And typically they will start with a dog type and they will end with slash body. Well, and the other thing of it is there is exactly one of them, right? Right, yes. You never render more than one of them. So that's how I like to kind of think about it. It's like, I'm going to have views, articles, index. And it may render various components. But the ultimate goal is to build one of these views. You're not usually rendering a component on its own. Though with Turbo, you might do that. You could almost call them templates and fragments or something like that. Or like views and partials. Your components are your partials, your views are your views, and layouts are just partials. This document would really benefit from a diagram of like, there's one box and it's a view, you know, and then inside there are many components, but then also view is a component. right it is a kind of a kind of component so if you scroll down there's a there's another option which is instead of through composition yes and that is like well what if we inherit from a a base view which is where we define the layout and you can see if you look at the base view that that you should swap the two of these these second and third taps and on the previous example because it's like going outside to inside Good feedback. The flow of abstraction or flow of execution. So in this one, we have this around template that calls super to render your template from the view. And so we're using Ruby inheritance and this around template callback to say that actually anything that is a view by default is just going to inherit this. this callback around the template. But my favorite way to do it is a kind of combination of inheritance and composition. So this is the most tricky one, but ultimately is the best one. You have a layout which is just a component hang on that sentence this is the best one should appear in the document yeah it is okay to be opinionated yeah you're right okay especially for beginners like and you know the first the opening intro of this can be like we're going to work up in complexity to what you should do yeah Yeah, that's good feedback. I really need to spend some time on these docs. But if you look in the layout, you'll see that it's just a plain component like we had in the first example. Yes. Right. It has an initializer that takes page info. Right. And it just has a template that yields. In the base view... we just waiting for your thing to update you can probably hear the mouse of me clicking right so we have an around template that renders layout.new right and notice it's calling this this method layout that doesn't exist And it's passing in page info, which is this page info object. And it's calling page title, which, again, doesn't exist. But if you click on View, you'll see that we define those methods. Yes. This is what I played with using, yeah. Yeah. This is my favorite kind of way to do it. And I like to actually put a default layout in the base view as well. Like def layout equals my default layout. But this gives you the most flexibility.
03:31:55Joel?
Yep.
Oh, you went quiet in the middle of a sentence because you went up at the end.
I was like, oh, did he just disappear again?
Okay.
Yeah.
And for me, I will have that page info is a literal data object that has various properties that I want.
Like it's going to have my open graph properties.
It's going to have a meta description tag.
It's going to have everything that I want to require to be defined on every page.
And then again, you can just pass that page info into a component to render the open graph tags or whatever.
Sure.
So, yeah, that's my preference.
And then the final thing is just the way that you can use a flex component to be a Rails layout that you can render standard Rails views inside of.
And you let Rails wrap the view in this thing.
But this is, like, the worst way to do it.
This is, like, if you've decided, I want to make my layout in flex, but I want to keep the rest of my views in ERB.
Yeah, and I actually...
I would assume there does, by the way, speaking of improving docs, if you have a pencil handy, there should be a converting a Rails app from ERB to Flex here that talks about...
Do you want to start from, pick a partial and make a component of it and then go up the stack?
Or do you want to start from the layout and go down the stack?
Exactly.
It should be opinionated and tell you which of those is the correct answer.
And I'm going to guess it's bottom up, but...
It is, yeah.
I would say...
pick the smallest component that you use the most and start there.
So button is a good one.
Why?
Because for very little effort, you get a lot of value.
You can go from having 100 different implementations of a button to having one implementation of a button and 100 different uses of it.
Sure.
Very quickly.
And yeah, I like to do it that way because it helps you think about the things that you repeat through your application and how you can standardize them, basically.
If you do it top down and you're like, okay, I'm going to have this one new page, it's going to be 100% flex.
Everything in it is going to be flex.
You end up... You have to learn all of these concepts at once, yeah.
Right, you have to learn all the concepts at once, but also you have to build probably 100 different components before you get even one page you can look at, which is exhausting, especially if you're not using them anywhere.
Whereas if you can build one component and get loads of value from it,
I think that's just a better way to do it.
That makes a lot of sense.
Because you either, if you do choose to do it the other way, your options are basically, I'm just going to take all the time to every time I approach something, like I'm adding a button.
Okay, now I need to go make a button component.
I'm adding a form.
Now I need to make a form component.
Now I'm adding a form item.
I need to make a form item.
I need to think about all of this stuff.
Like, it's just a lot.
And you end up, you know, adding this one view, you've created, your diff is like huge and you've created 50 different files and you can't really see the value there.
The value is from reuse.
Okay.
Or what you do is you don't extract components, and you end up with a FlexView that just has a very deeply nested tree of HTML, which isn't really what you ever want.
The ideal is that your views basically don't have any raw HTML in them.
They're only calling out to components like...
yeah like you don't have an h1 you have a header component that's your header it's got everything all of your customizations about what headers are for you and your limitations right the other thing is that would be especially useful and i'm thinking here of larger teams and projects that are larger than lobsters is a
command that gives you stats on how many erb files and how many flex components there are well really it's how many erbs there are and i'm aware that's just a find command but a big thing on like if flex wants to eat a site is you have to define a ratchet right and you can say well we're going to make a rule that pull requests
can introduce components, but they can't introduce new ERB files on net.
And so if you really have to introduce an ERB file, make an extra component because you have to have a ratchet in a large team.
And then the build server can enforce that of, well, your PR can't break that rule or has to get clearance from the manager kind of thing.
That's a really good idea.
I like that.
There could be like a bin slash Rails G flex shit list that creates a list of all of the existing .erb files.
Yeah.
And those are the only ones that are allowed.
Yeah.
That kind of thing.
So there was a nice article.
pushcx https://push.cx/large-refactors
minute ago about doing it called them long-term refactors but a lot of them are once your code base is so big you cannot do it in one pr and yeah i was previously working at stripe which had something like 50 million lines of code and the ruby code base was not all of that but it was 50 million
Yeah, total between the live code and then the data analysis and the test and there was a lot of code.
Maybe I typoed it.
It was 1.5?
Oh, it's been long enough.
I don't remember anymore.
Maybe it was 15.
I remember that when I was there, the Shopify code base was, I think, just over 1 million lines of Ruby.
Stripe?
but that was just Shopify core.
But then I've heard since then that it's like 3 million lines of Ruby.
and it's, it's probably even more than that now.
Yeah.
I can't, I can't remember.
You would have to ask somebody who's there, but like the long, even if I've, I should put a footnote on this, that I, that doesn't feel right anymore.
yeah, honestly, it could be a typo for just five.
that's, that's frustrating anyways.
The refactors, because they were so big and cross-cutting, like multiple teams had to do it.
There always had to be some kind of software ratchet installed in the build server so that pull requests were forcing things along.
And then I left a comment on it.
So I just wanted to throw that in there.
No, it's a really good point.
And also the other way, which is like success.
stats if you're working on yes you have to have like a burndown chart yeah yeah we we did that when i was converting i fully converted or almost fully converted the clear scope code base to flex sure and it was yeah every single day going and looking at like
I would just look at flexhtml.descendants.count, I think, because that does it if you load your Rails app.
And just seeing the numbers go up is pretty encouraging when you're working on something for that long that's that big.
Absolutely.
We also introduced literal there, and that was even quicker, though.
We got rid of, I think, all but one def initialize.
we completely got rid of and replaced with these props.
It meant that our error rate went down massively in production.
We very rarely had an error in production, even though we ran the type checking in production, so it meant that the code was more likely to error in production.
Yeah, we have a few initializers that some of them are like, well, we're just going to assign some variables, but some of them have to do some work.
Yeah, you can do that in after initialize, which the tool calls after it's done.
Yeah, like this one is, it takes the thing that you have up in the controller and then it converts it to the data structure it wants.
yeah so the way that i like to do these oh yeah for the search model that's the big one i'm thinking of yeah we have to do a ton of you passed in the raw string that the user searched for and then we parse it and then build out all the stuff we need to do to run a query so i would make this method def self dot from params or something like that
sure and then have it call new once it had figured out what all the parameters were going to be sure so have like a class method that's a specialized initializer well or the other thing you can do is so we would have you know all these props right and then
There's a lot of long code.
You can overwrite initialize and call super.
Yeah, I figured.
But as long as I'm showing off bad code, instead of saying, you know, putting it inside the end, you could also say search.
Because there are different namespaces in rubies.
03:43:09Right. So you could name a function search, and then instead of initializing with search.new, you would initialize with search. Yep. I have been waiting for an opportunity to use this idiom. Yeah. Yeah. And I've never had one cause I, I haven't, you know, I've been doing app code rather than plumbing utility kind of code, but I put this one in my back pocket the first time I left off a new and I worked through the error message. This is, now I want you to have it and you can call it heinous initializers. Yeah. Yeah, no, I like it. And it's like you export a specialized constructor essentially. Yeah. Oh, do you hear some amount of like encapsulation along those lines is coming to Ruby? Yeah, that would be cool. it was Matt's just mentioned it. Yeah. I want it. I'm writing a test library. And so I don't, if I, if I require something, even, this isn't a problem anymore, but even like require set, if you think about that, it's possible that my test library has just made your, your, application passes tests where it normally wouldn't have done. For example, if you had forgotten to require a set. Yes. Yes. And so I'm like, I'm really trying to avoid using dependencies and things like that inside or even requiring things from Rails standard library inside Quick Draw because if I do, that's another way that you can have a false positive test run. Yeah. Because it's just the one global namespace. If I could namespace my imports, then it would be much better. So the one... I have to express an opinion and you can just make a polite noise and ignore it. Directories and classes should always be named in the singular because it is maddening to remember, is this views articles index or indexes? And is the directory named, is it app or apps? Is it app component or app components? And. The answer is it should just always be singular. And you can just say, thanks for sharing your opinion and blow it out. Like this is like the most tabs for spaces opinion, but I will like die on this hill of they should just always be singular. The very specific reason why we've not done this is you would have to escape every time you referenced your article model. if we had views articles index, sorry, views article index, and then you said prop article article, you'd be referencing the module that your index was in instead of the model. And so you'd have to do a... I'd have prop article article. Oh, what you're saying, in the component, In the component, you'd be referencing the module that that was sitting in. Right, because Rails just dumped them all in the global namespace instead of like... Instead of like model article. So we could continue shaking our fist at DHH over this one. Yeah, you could do that. Right. But otherwise, yeah. So instead, we try to follow the way the controllers are named and the way that the actual folders would be named, which is also compatible with just with how... What is it called? Site work just works by default. Right. Well, but site work is implemented the way it is to match the Rails convention. Yeah. Yeah. It's frustrating, but you get used to it really quickly. I can say that. I have been writing Rails code for 20 years, and I'm not used to it. I cannot remember which things are singular and which are plural. And I am constantly typing like app slash model slash page because I want the page model, not the page models. Yeah. I don't know. Maybe it's just the one thing that endlessly grates on me when I do Rails. And I realize it's so petty, but... it never has become comfortable for me. So I was hoping to persuade you before, but that's actually a pretty good reason of if you match your model name, because Rails dumps in the top level. And it's so common to need to reference the model from the component that's named the same that I was running into it all the time. And so that's why this was done this way. But yeah, I agree with you. Yeah, or it would be, like, prop... That would work, right? And I'm assuming, you know, we're into the, like... Yeah, that would totally work. But you have to remember to do it, which is super annoying. Yeah, that's fair. And this is, like, an opinionated style thing, so, you know, I'm not actually going to have a knockdown dragout fight of it. And I see it's like a, well, you're making the best of... where you're at. Another advantage of props like this is if you do prop class, it will actually work. To just say anything? So if you go back to the code... I could bring the example up, yeah. So if I said class here on my prop article... so no i mean do do a prop called class as in css class sure now if you think about the initializer that this would generate well i'm guessing but do i want this to be a string or an array just make it a string for now all right sure right but imagine this the initializer that it has to generate oh yes it has to remember to escape class, right? Right, because if you were to if you were to make an initializer by hand, you have to do binding instance. We've all done this one where, you know, yeah. Sorry, local variable get. Or you take keyword arguments as options and you key into them. Right. Yeah. Yeah. Yeah, you don't get what you want out of that. Even that won't work. How do you reference the class variable that you... Yeah. See, that will fail. Yeah. So what it will generate is binding.localVariableGet class automatically. That's nice. Which is really nice when you're using Flex because you just run into it all the time.
03:50:46Hmm.
All right, so...
I really appreciate you jumping on this, to answer your question about how often I stream, since I do it two days a week, I tend to do two, three hour streams and we've blown about 50 minutes past that.
That's fine.
Cause I didn't have dinner plans.
I checked my calendar very quietly.
but it does mean I'm not going to jump into like, let's start making a comment,
arh68 ya thx for your time joel PopGhost
component right now so the thing that happens is also after like two and a half three hours of keeping up the patter of what am i doing and why my brain melts and i'm starting to get there now which is why i couldn't remember the word component so i'm gonna wind down the stream and then can you just stay on the signal call and let's chat about do we want to do that on thursday that could be a lot of fun yeah that sounds great yeah so
ARH, yeah, thank you.
Thanks for hanging out, folks.
This has been a very unexpectedly different direction than my actual plan, but a lot more fun.
And yeah, not totally convinced on SQLite, but Flex is attractive.
Did you just click in your Vim?
arh68 GlitchCat cool stream bro
Oh, I have so... Come on, my VimRC is like...
No, but how do they support that?
Because I want to make a CLI that has clickable elements.
Set mouse equals A in Vim.
And I can click to move the cursor.
I can...
Frici thx for the stream and demonstration folks π
But how do they support that?
What is the API called?
It's been so long since I've written...
As in, could I make my own terminal UI that supported clicks?
Yeah, and it's cross-platform, so I'm running Alacrity, and this would also work in, honestly, any term that has been put out since, like, 1995.
I'll tell you what I want to use this for.
Yeah.
When you do an assert equals in QuickDraw, and it gives you an error, you asserted that this was going to be this, and it wasn't.
I want to have a button that you can click that goes and updates the test to expect the thing that it actually got.
Well, that's nice for golden master testing.
It's such a foot gone otherwise.
I follow.
My testing process usually is I'll write the test and I'll say assert equals and I'll just put an empty string because I want it to fail and I want to see that the actual was the real thing I wanted it to be.
And then I'll just copy paste it in.
Yes, you could do that.
But now that I'm doing this, I'm doing like diffing with DiffTastic, it's really awkward to copy and paste the error message back into your test.
The problem with this is, if you want to grab mouse events, your process has to be running, and I'm assuming QuickDraw exits at the end of its run.
Yeah, but it has a watch mode.
Oh, okay.
Well, then, yeah, you're all set there.
It could keep running.
Yeah, you're all set there.
Because then you can own the terminal.
Yes.
Okay.
Yeah, I'm going to try to make a terminal UI for the watch mode.
I'm glad to show this off.
mjiig Ocaml expect tests are sort of meant to be used this way, and it's really really nice
Where it can give you an error, and then you click a button, and it fixes the error by changing your test.
It's funny, every third stream, someone comes on and is like, why don't you have syntax highlighting?
Because they don't recognize the italics and stuff.
But then the thing that blew your mind was I used the mouse.
mjiig (Though I've only used it with emacs integration, not a TUI)
So yeah, the other end of the stream, and thanks for hanging out, folks, is next stream will be Thursday at 9 AM Chicago time.
and oh yeah i didn't mention it but there was no news about the online safety act that wants to make forums illegal and then that's about that yeah thanks for coming and hanging out so i'm gonna click stop stream and joel and i are gonna conspire and hopefully he can come back thursday morning and we will experiment take care folks